framework-agents 0.0.0-stage → 0.1.0

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/index.js ADDED
@@ -0,0 +1,3951 @@
1
+ import {
2
+ Agent,
3
+ AgentError,
4
+ AgentState,
5
+ AudioBlockSchema,
6
+ ChatMessageSchema,
7
+ ClearToolUsesEdit,
8
+ ContentBlockSchema,
9
+ FileBlockSchema,
10
+ FileMemory,
11
+ HitlInterrupt,
12
+ ImageBlockSchema,
13
+ InMemoryMemory,
14
+ InMemoryStorage,
15
+ LocalFileStorage,
16
+ NonStandardBlockSchema,
17
+ NullConversationManager,
18
+ ReasoningBlockSchema,
19
+ SessionManager,
20
+ SlidingWindowConversationManager,
21
+ SummarizingConversationManager,
22
+ TextBlockSchema,
23
+ ToolCallBlockSchema,
24
+ ToolRegistry,
25
+ ToolRejection,
26
+ assistant,
27
+ backoffDelayMs,
28
+ contextEditingMiddleware,
29
+ createMiddleware,
30
+ createSubAgentMiddleware,
31
+ defaultToResult,
32
+ defineTool,
33
+ deserializeMessages,
34
+ estimateCostUsd,
35
+ estimateTokens,
36
+ filesystemMiddleware,
37
+ filesystemTools,
38
+ getRetryable,
39
+ hasImages,
40
+ historyTokens,
41
+ hitlKey,
42
+ humanInTheLoopMiddleware,
43
+ imagesOf,
44
+ llmToolSelectorMiddleware,
45
+ messageTokens,
46
+ modelCallLimit,
47
+ modelFallbackMiddleware,
48
+ modelRetryMiddleware,
49
+ parseStructured,
50
+ piiMiddleware,
51
+ priceFor,
52
+ providerStrategy,
53
+ responseFormatJsonSchema,
54
+ responseFormatName,
55
+ runWithBudget,
56
+ selectSnapshotFields,
57
+ serializeMessages,
58
+ sleep,
59
+ stampRetryable,
60
+ summarizationMiddleware,
61
+ system,
62
+ textOf,
63
+ todoListMiddleware,
64
+ toolCallLimitMiddleware,
65
+ toolEmulatorMiddleware,
66
+ toolResult,
67
+ toolRetry,
68
+ toolRetryMiddleware,
69
+ toolStrategy,
70
+ trimDangling,
71
+ usageCost,
72
+ user,
73
+ wireTextOf
74
+ } from "./chunk-77OZE2QD.js";
75
+
76
+ // src/config/index.ts
77
+ import { existsSync } from "fs";
78
+ import { dirname, join, resolve } from "path";
79
+ import { config } from "dotenv";
80
+ import { z } from "zod";
81
+ function loadEnv() {
82
+ if (process.env.FRAMEWORK_ENV_PATH) {
83
+ config({ path: process.env.FRAMEWORK_ENV_PATH });
84
+ return;
85
+ }
86
+ config();
87
+ let dir = resolve(process.cwd());
88
+ for (let i = 0; i < 3; i++) {
89
+ dir = dirname(dir);
90
+ const candidate = join(dir, ".env");
91
+ if (existsSync(candidate)) {
92
+ config({ path: candidate });
93
+ return;
94
+ }
95
+ }
96
+ }
97
+ loadEnv();
98
+ var ConfigSchema = z.object({
99
+ TOGETHER_API_KEY: z.string().default(""),
100
+ TOGETHER_BASE_URL: z.string().default("https://api.together.xyz/v1"),
101
+ /** Small chat default — one model, no tiers. */
102
+ TOGETHER_MODEL: z.string().default("Qwen/Qwen3.5-9B"),
103
+ OPENAI_BASE_URL: z.string().default("https://api.openai.com/v1"),
104
+ OPENAI_API_KEY: z.string().default(""),
105
+ OPENAI_MODEL: z.string().default("gpt-4o-mini"),
106
+ ANTHROPIC_API_KEY: z.string().default(""),
107
+ ANTHROPIC_BASE_URL: z.string().default("https://api.anthropic.com"),
108
+ ANTHROPIC_MODEL: z.string().default("claude-sonnet-4-6"),
109
+ OLLAMA_BASE_URL: z.string().default("http://localhost:11434/v1"),
110
+ OLLAMA_MODEL: z.string().default("qwen2.5"),
111
+ E2E_MODEL: z.string().default("")
112
+ });
113
+ var cached;
114
+ function loadConfig(env = process.env) {
115
+ if (cached) return cached;
116
+ const parsed = ConfigSchema.parse({
117
+ TOGETHER_API_KEY: env.TOGETHER_API_KEY ?? "",
118
+ TOGETHER_BASE_URL: env.TOGETHER_BASE_URL,
119
+ TOGETHER_MODEL: env.TOGETHER_MODEL,
120
+ OPENAI_BASE_URL: env.OPENAI_BASE_URL,
121
+ OPENAI_API_KEY: env.OPENAI_API_KEY ?? "",
122
+ OPENAI_MODEL: env.OPENAI_MODEL,
123
+ ANTHROPIC_API_KEY: env.ANTHROPIC_API_KEY ?? "",
124
+ ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL,
125
+ ANTHROPIC_MODEL: env.ANTHROPIC_MODEL,
126
+ OLLAMA_BASE_URL: env.OLLAMA_BASE_URL,
127
+ OLLAMA_MODEL: env.OLLAMA_MODEL,
128
+ E2E_MODEL: env.E2E_MODEL ?? ""
129
+ });
130
+ const useTogether = parsed.TOGETHER_API_KEY.length > 0;
131
+ const model = useTogether ? parsed.TOGETHER_MODEL : parsed.OPENAI_MODEL;
132
+ cached = {
133
+ ...parsed,
134
+ active: useTogether ? { baseUrl: parsed.TOGETHER_BASE_URL, apiKey: parsed.TOGETHER_API_KEY, model, source: "together" } : { baseUrl: parsed.OPENAI_BASE_URL, apiKey: parsed.OPENAI_API_KEY, model, source: "openai" }
135
+ };
136
+ return cached;
137
+ }
138
+ function __resetConfig() {
139
+ cached = void 0;
140
+ }
141
+
142
+ // src/checkpoint/index.ts
143
+ import { mkdir, open, readFile, stat, unlink, writeFile } from "fs/promises";
144
+ import { join as join2 } from "path";
145
+ async function withFileLock(lockPath, fn, opts = {}) {
146
+ const staleMs = opts.staleMs ?? 3e4;
147
+ await mkdir(join2(lockPath, ".."), { recursive: true });
148
+ for (; ; ) {
149
+ try {
150
+ const handle = await open(lockPath, "wx");
151
+ await handle.close();
152
+ break;
153
+ } catch (err) {
154
+ const code = err.code;
155
+ if (code !== "EEXIST") throw err;
156
+ try {
157
+ const info = await stat(lockPath);
158
+ if (Date.now() - info.mtimeMs < staleMs) {
159
+ throw new Error(`Lock held: ${lockPath}`);
160
+ }
161
+ } catch (statErr) {
162
+ if (statErr.code !== "ENOENT") throw statErr;
163
+ }
164
+ try {
165
+ await unlink(lockPath);
166
+ } catch {
167
+ }
168
+ }
169
+ }
170
+ try {
171
+ return await fn();
172
+ } finally {
173
+ try {
174
+ await unlink(lockPath);
175
+ } catch {
176
+ }
177
+ }
178
+ }
179
+ var FileCheckpointer = class {
180
+ constructor(root) {
181
+ this.root = root;
182
+ }
183
+ root;
184
+ /** threadId lands in a filesystem path — keep it a single safe directory name. */
185
+ threadDir(threadId) {
186
+ if (threadId === "" || threadId === "." || threadId === ".." || threadId.includes("/")) {
187
+ throw new AgentError("THREAD_ID_INVALID", `Invalid threadId: ${JSON.stringify(threadId)}`, false);
188
+ }
189
+ return join2(this.root, "threads", threadId);
190
+ }
191
+ file(threadId) {
192
+ return join2(this.threadDir(threadId), "checkpoint.json");
193
+ }
194
+ lock(threadId) {
195
+ return join2(this.threadDir(threadId), ".lock");
196
+ }
197
+ async save(threadId, checkpoint) {
198
+ const record = { threadId, savedAt: (/* @__PURE__ */ new Date()).toISOString(), ...checkpoint };
199
+ await withFileLock(this.lock(threadId), async () => {
200
+ await mkdir(join2(this.file(threadId), ".."), { recursive: true });
201
+ await writeFile(this.file(threadId), JSON.stringify(record), "utf8");
202
+ });
203
+ }
204
+ async load(threadId) {
205
+ return withFileLock(this.lock(threadId), async () => {
206
+ try {
207
+ return JSON.parse(await readFile(this.file(threadId), "utf8"));
208
+ } catch {
209
+ return void 0;
210
+ }
211
+ });
212
+ }
213
+ async delete(threadId) {
214
+ await withFileLock(this.lock(threadId), async () => {
215
+ try {
216
+ await unlink(this.file(threadId));
217
+ } catch {
218
+ }
219
+ });
220
+ }
221
+ };
222
+
223
+ // src/swarm/index.ts
224
+ import { z as z2 } from "zod";
225
+ var HANDOFF_TOOL = "handoff_to_agent";
226
+ function handoffSignal(to, message, context) {
227
+ const err = new AgentError("SWARM_HANDOFF", `Handoff to ${to}`, false);
228
+ err.handoff = { to, message, context };
229
+ return err;
230
+ }
231
+ function readHandoffSignal(err) {
232
+ if (err instanceof AgentError && err.code === "SWARM_HANDOFF") {
233
+ return err.handoff;
234
+ }
235
+ return void 0;
236
+ }
237
+ function combineAbort(parent, timeoutMs) {
238
+ if (!parent && !timeoutMs) return void 0;
239
+ const controller = new AbortController();
240
+ if (parent) {
241
+ if (parent.aborted) controller.abort();
242
+ else parent.addEventListener("abort", () => controller.abort(), { once: true });
243
+ }
244
+ if (timeoutMs) {
245
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
246
+ controller.signal.addEventListener("abort", () => clearTimeout(timer), { once: true });
247
+ }
248
+ return controller.signal;
249
+ }
250
+ var Swarm = class {
251
+ agents;
252
+ entryPoint;
253
+ maxHandoffs;
254
+ maxIterations;
255
+ executionTimeoutMs;
256
+ nodeTimeoutMs;
257
+ loopWindow;
258
+ minUniqueAgents;
259
+ constructor(opts) {
260
+ if (!opts.agents.length) throw new AgentError("SWARM_EMPTY", "Swarm needs at least one agent", false);
261
+ this.agents = opts.agents;
262
+ this.entryPoint = typeof opts.entryPoint === "string" ? opts.agents.find((a) => a.name === opts.entryPoint) ?? (() => {
263
+ throw new AgentError("SWARM_UNKNOWN_ENTRY", `Unknown entry point: ${opts.entryPoint}`, false);
264
+ })() : opts.entryPoint ?? opts.agents[0];
265
+ this.maxHandoffs = opts.maxHandoffs ?? 20;
266
+ this.maxIterations = opts.maxIterations ?? 20;
267
+ this.executionTimeoutMs = opts.executionTimeoutMs;
268
+ this.nodeTimeoutMs = opts.nodeTimeoutMs;
269
+ this.loopWindow = opts.loopDetectionWindow ?? 0;
270
+ this.minUniqueAgents = opts.minUniqueAgents ?? (this.loopWindow > 0 ? 2 : 0);
271
+ }
272
+ roster(except) {
273
+ return this.agents.filter((a) => a.name !== except).map((a) => `- ${a.name}${a.description ? `: ${a.description}` : ""}`).join("\n");
274
+ }
275
+ buildInput(task, history, shared, message, current) {
276
+ const knowledge = Object.entries(shared).map(([agent, facts]) => `\u2022 ${agent}: ${JSON.stringify(facts)}`).join("\n");
277
+ return [
278
+ `Original task: ${task}`,
279
+ message ? `Handoff message: ${message}` : "",
280
+ history.length ? `Agents so far: ${history.join(" \u2192 ")}` : "",
281
+ knowledge ? `Shared knowledge:
282
+ ${knowledge}` : "",
283
+ `Available collaborators:
284
+ ${this.roster(current)}`,
285
+ `If you need a collaborator, call ${HANDOFF_TOOL} with their name. Otherwise finish with your final answer as text.`
286
+ ].filter(Boolean).join("\n\n");
287
+ }
288
+ memberWithHandoff(agent, shared) {
289
+ const handoff = defineTool({
290
+ name: HANDOFF_TOOL,
291
+ description: `Hand control to a specialist. Args: to (agent name), message (what you need), context (facts to share as an object).`,
292
+ schema: z2.object({ to: z2.string(), message: z2.string(), context: z2.record(z2.unknown()).optional() }),
293
+ execute: async ({ to, message, context }) => {
294
+ if (!this.agents.some((a) => a.name === to)) {
295
+ return `Error: unknown agent "${to}". Available: ${this.agents.map((a) => a.name).join(", ")}. Pick one or finish yourself.`;
296
+ }
297
+ if (context && Object.keys(context).length) shared[agent.name] = { ...shared[agent.name] ?? {}, ...context };
298
+ throw handoffSignal(to, message, context);
299
+ }
300
+ });
301
+ return agent.withTools(handoff);
302
+ }
303
+ async run(task, opts) {
304
+ const stream = this.stream(task, opts);
305
+ for (; ; ) {
306
+ const next = await stream.next();
307
+ if (next.done) return next.value;
308
+ }
309
+ }
310
+ async *stream(task, opts) {
311
+ const taskText = typeof task === "string" ? task : task.map((b) => b.type === "text" ? b.text : `[${b.type}]`).join("\n");
312
+ const invocationState = opts?.sharedState ?? {};
313
+ const execSignal = combineAbort(opts?.signal, this.executionTimeoutMs);
314
+ const shared = {};
315
+ const history = [];
316
+ const results = {};
317
+ const usage = { inputTokens: 0, outputTokens: 0 };
318
+ const handoffTrail = [];
319
+ let current = this.entryPoint;
320
+ let message = "";
321
+ let iterations = 0;
322
+ let handoffs = 0;
323
+ let finalText = "";
324
+ const fail = (error) => this.finish("FAILED", error, history, results, usage, shared, iterations, handoffs);
325
+ for (; ; ) {
326
+ iterations += 1;
327
+ if (iterations > this.maxIterations) {
328
+ return fail(`Exceeded maxIterations (${this.maxIterations})`);
329
+ }
330
+ yield { type: "node_start", agent: current.name };
331
+ const runner = this.memberWithHandoff(current, shared);
332
+ const nodeSignal = combineAbort(execSignal, this.nodeTimeoutMs);
333
+ let result;
334
+ try {
335
+ const gen = runner.stream(this.buildInput(taskText, history, shared, message, current.name), {
336
+ signal: nodeSignal,
337
+ invocationState
338
+ });
339
+ for (; ; ) {
340
+ const next = await runWithBudget(() => gen.next(), { signal: execSignal });
341
+ if (next.done) {
342
+ result = next.value;
343
+ break;
344
+ }
345
+ yield { type: "agent_event", agent: current.name, event: next.value };
346
+ }
347
+ } catch (err) {
348
+ const handoff = readHandoffSignal(err);
349
+ if (!handoff) {
350
+ const messageText = err instanceof Error ? err.message : String(err);
351
+ return fail(`Agent ${current.name} failed: ${messageText}`);
352
+ }
353
+ handoffs += 1;
354
+ if (handoffs > this.maxHandoffs) {
355
+ return fail(`Exceeded maxHandoffs (${this.maxHandoffs})`);
356
+ }
357
+ handoffTrail.push(current.name, handoff.to);
358
+ yield { type: "handoff", from: current.name, to: handoff.to };
359
+ const loop = this.detectLoop(handoffTrail);
360
+ if (loop) {
361
+ return fail(loop);
362
+ }
363
+ const next = this.agents.find((a) => a.name === handoff.to);
364
+ if (!next) {
365
+ return fail(`Handoff to unknown agent: ${handoff.to}`);
366
+ }
367
+ history.push(current.name);
368
+ message = handoff.message;
369
+ current = next;
370
+ continue;
371
+ }
372
+ if (!result) return fail(`Agent ${current.name} returned no result`);
373
+ history.push(current.name);
374
+ results[current.name] = result;
375
+ usage.inputTokens += result.usage.inputTokens;
376
+ usage.outputTokens += result.usage.outputTokens;
377
+ usage.reasoningTokens = (usage.reasoningTokens ?? 0) + (result.usage.reasoningTokens ?? 0);
378
+ finalText = result.text;
379
+ yield { type: "node_done", agent: current.name };
380
+ const done = this.finish("COMPLETED", "", history, results, usage, shared, iterations, handoffs, finalText);
381
+ yield { type: "result", result: done };
382
+ return done;
383
+ }
384
+ }
385
+ detectLoop(trail) {
386
+ if (this.loopWindow <= 0 || this.minUniqueAgents <= 0) return void 0;
387
+ const window = trail.slice(-this.loopWindow);
388
+ if (window.length < this.loopWindow) return void 0;
389
+ if (new Set(window).size < this.minUniqueAgents) {
390
+ return `Repetitive handoff detected (${window.join(" \u2192 ")}): fewer than ${this.minUniqueAgents} unique agents in the last ${this.loopWindow} trail entries`;
391
+ }
392
+ return void 0;
393
+ }
394
+ finish(status, error, history, results, usage, shared, iterations, handoffs, text = "") {
395
+ return {
396
+ status,
397
+ text,
398
+ nodeHistory: [...history],
399
+ results,
400
+ iterations,
401
+ handoffs,
402
+ usage,
403
+ sharedContext: shared,
404
+ ...error ? { error } : {}
405
+ };
406
+ }
407
+ };
408
+
409
+ // src/a2a/index.ts
410
+ import { createServer } from "http";
411
+ import { z as z3 } from "zod";
412
+ function textOfParts(parts) {
413
+ if (!Array.isArray(parts)) return "";
414
+ return parts.map((p) => {
415
+ if (typeof p === "string") return p;
416
+ if (p && typeof p === "object") {
417
+ const o = p;
418
+ if (typeof o.text === "string") return o.text;
419
+ }
420
+ return "";
421
+ }).join("");
422
+ }
423
+ function parseSendResult(result) {
424
+ if (typeof result === "string") return result;
425
+ if (result && typeof result === "object") {
426
+ const o = result;
427
+ const msg = o.message;
428
+ if (msg?.parts) return textOfParts(msg.parts);
429
+ if (typeof o.text === "string") return o.text;
430
+ return JSON.stringify(result);
431
+ }
432
+ return String(result ?? "");
433
+ }
434
+ var A2AClient = class {
435
+ constructor(endpoint, opts = {}) {
436
+ this.endpoint = endpoint;
437
+ this.endpoint = endpoint.replace(/\/$/, "");
438
+ this.fetchFn = opts.fetchFn ?? fetch;
439
+ this.timeoutMs = opts.timeoutMs ?? 3e5;
440
+ }
441
+ endpoint;
442
+ fetchFn;
443
+ timeoutMs;
444
+ async getCard() {
445
+ let res;
446
+ try {
447
+ res = await this.fetchFn(`${this.endpoint}/.well-known/agent-card.json`, {
448
+ headers: { accept: "application/json" },
449
+ signal: AbortSignal.timeout(this.timeoutMs)
450
+ });
451
+ } catch (err) {
452
+ throw new AgentError("A2A_TRANSPORT", `Agent card fetch failed: ${err instanceof Error ? err.message : String(err)}`, true);
453
+ }
454
+ if (!res.ok) throw new AgentError("A2A_CARD", `Agent card fetch failed: HTTP ${res.status}`, res.status >= 500);
455
+ const card = await res.json();
456
+ if (!card?.name) throw new AgentError("A2A_CARD", "Invalid agent card (missing name)", false);
457
+ return card;
458
+ }
459
+ async send(text, contextId) {
460
+ let res;
461
+ try {
462
+ res = await this.fetchFn(this.endpoint, {
463
+ method: "POST",
464
+ headers: { "content-type": "application/json", accept: "application/json" },
465
+ body: JSON.stringify({
466
+ jsonrpc: "2.0",
467
+ id: `a2a-${Date.now().toString(36)}`,
468
+ method: "message/send",
469
+ params: {
470
+ message: { role: "user", parts: [{ kind: "text", text }] },
471
+ ...contextId ? { contextId } : {}
472
+ }
473
+ }),
474
+ signal: AbortSignal.timeout(this.timeoutMs)
475
+ });
476
+ } catch (err) {
477
+ throw new AgentError("A2A_TRANSPORT", `A2A send failed: ${err instanceof Error ? err.message : String(err)}`, true);
478
+ }
479
+ if (!res.ok) throw new AgentError("A2A_TRANSPORT", `A2A HTTP ${res.status}`, res.status >= 500);
480
+ const rpc = await res.json();
481
+ if (rpc.error) throw new AgentError("A2A_ERROR", `Remote agent error: ${rpc.error.message}`, false);
482
+ const resultContextId = rpc.result && typeof rpc.result === "object" ? rpc.result.contextId : void 0;
483
+ return { text: parseSendResult(rpc.result), contextId: resultContextId ?? contextId };
484
+ }
485
+ };
486
+ function a2aAsTool(client, opts = {}) {
487
+ return defineTool({
488
+ name: opts.name ?? "remote_agent",
489
+ description: opts.description ?? `Remote A2A agent at ${client.endpoint}`,
490
+ schema: z3.object({ input: z3.string(), contextId: z3.string().optional() }),
491
+ execute: async ({ input, contextId }) => (await client.send(input, contextId)).text
492
+ });
493
+ }
494
+ var A2AServer = class {
495
+ constructor(opts) {
496
+ this.opts = opts;
497
+ this.maxContexts = Math.max(1, opts.maxContexts ?? 1e3);
498
+ }
499
+ opts;
500
+ server;
501
+ contexts = /* @__PURE__ */ new Map();
502
+ maxContexts;
503
+ corsHeaders(origin) {
504
+ const allowed = this.opts.corsOrigins ?? [];
505
+ if (!allowed.length || !origin) return {};
506
+ if (!allowed.includes("*") && !allowed.includes(origin)) return {};
507
+ return {
508
+ "access-control-allow-origin": allowed.includes("*") ? "*" : origin,
509
+ "access-control-allow-methods": "GET, POST, OPTIONS",
510
+ "access-control-allow-headers": "content-type, authorization"
511
+ };
512
+ }
513
+ runOpts(contextId) {
514
+ return this.opts.checkpointer ? { checkpointer: this.opts.checkpointer, threadId: contextId } : {};
515
+ }
516
+ async agentFor(contextId) {
517
+ const existing = this.contexts.get(contextId);
518
+ if (existing) {
519
+ this.contexts.delete(contextId);
520
+ this.contexts.set(contextId, existing);
521
+ return existing;
522
+ }
523
+ const agent = await this.opts.agentFactory(contextId);
524
+ this.contexts.set(contextId, agent);
525
+ while (this.contexts.size > this.maxContexts) {
526
+ const oldest = this.contexts.keys().next().value;
527
+ if (oldest === void 0) break;
528
+ this.contexts.delete(oldest);
529
+ }
530
+ return agent;
531
+ }
532
+ /** Read a request body with a hard cap; JSON.parse errors to the caller. */
533
+ readBody(req) {
534
+ const max = this.opts.maxBodyBytes ?? 10 * 1024 * 1024;
535
+ return new Promise((resolve4, reject) => {
536
+ let data = "";
537
+ req.on("data", (c) => {
538
+ data += c;
539
+ if (data.length > max) {
540
+ req.destroy();
541
+ reject(new AgentError("A2A_BODY_TOO_LARGE", `Request body exceeds ${max} bytes`, false));
542
+ }
543
+ });
544
+ req.on("end", () => resolve4(data));
545
+ req.on("error", reject);
546
+ });
547
+ }
548
+ async handle(req, res) {
549
+ const cors = this.corsHeaders(req.headers.origin);
550
+ const send = (code, body) => {
551
+ res.writeHead(code, { "content-type": "application/json", ...cors });
552
+ res.end(JSON.stringify(body));
553
+ };
554
+ try {
555
+ if (req.method === "OPTIONS") {
556
+ res.writeHead(204, { ...cors });
557
+ res.end();
558
+ return;
559
+ }
560
+ if (req.method === "GET" && (req.url === "/ok" || req.url === "/health")) {
561
+ send(200, { ok: true });
562
+ return;
563
+ }
564
+ if (req.method === "GET" && req.url === "/.well-known/agent-card.json") {
565
+ send(200, this.opts.card);
566
+ return;
567
+ }
568
+ if (req.method === "POST" && (req.url === "/" || req.url === "")) {
569
+ const rpc = JSON.parse(await this.readBody(req));
570
+ if (rpc.method !== "message/send") {
571
+ send(200, { jsonrpc: "2.0", id: rpc.id, error: { code: -32601, message: `Unknown method: ${rpc.method}` } });
572
+ return;
573
+ }
574
+ const contextId = rpc.params?.contextId ?? `ctx-${Date.now().toString(36)}`;
575
+ const text = textOfParts(rpc.params?.message?.parts);
576
+ const result = await (await this.agentFor(contextId)).run(text, this.runOpts(contextId));
577
+ send(200, {
578
+ jsonrpc: "2.0",
579
+ id: rpc.id,
580
+ result: { message: { role: "agent", parts: [{ kind: "text", text: result.text }] }, contextId }
581
+ });
582
+ return;
583
+ }
584
+ if (req.method === "POST" && req.url === "/stream") {
585
+ const rpc = JSON.parse(await this.readBody(req));
586
+ const contextId = rpc.params?.contextId ?? `ctx-${Date.now().toString(36)}`;
587
+ const text = textOfParts(rpc.params?.message?.parts);
588
+ res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
589
+ const write = (payload) => {
590
+ res.write(`data: ${JSON.stringify(payload)}
591
+
592
+ `);
593
+ };
594
+ const disconnect = new AbortController();
595
+ res.on("close", () => {
596
+ if (!res.writableEnded) disconnect.abort();
597
+ });
598
+ try {
599
+ const gen = (await this.agentFor(contextId)).stream(text, {
600
+ signal: disconnect.signal,
601
+ ...this.runOpts(contextId)
602
+ });
603
+ for (; ; ) {
604
+ const next = await gen.next();
605
+ if (next.done) {
606
+ write({ contextId, done: true, text: next.value.text });
607
+ break;
608
+ }
609
+ write({ contextId, event: next.value });
610
+ }
611
+ } catch (err) {
612
+ write({ contextId, error: err instanceof Error ? err.message : String(err) });
613
+ }
614
+ write("[DONE]");
615
+ res.end();
616
+ return;
617
+ }
618
+ send(404, { error: "not found" });
619
+ } catch (err) {
620
+ const message = err instanceof Error ? err.message : String(err);
621
+ if (res.headersSent) {
622
+ try {
623
+ res.write(`data: ${JSON.stringify({ error: message })}
624
+
625
+ `);
626
+ res.write("data: [DONE]\n\n");
627
+ res.end();
628
+ } catch {
629
+ }
630
+ return;
631
+ }
632
+ send(
633
+ err instanceof AgentError && err.code === "A2A_BODY_TOO_LARGE" ? 413 : 200,
634
+ { jsonrpc: "2.0", id: null, error: { code: -32603, message } }
635
+ );
636
+ }
637
+ }
638
+ async start(port, host) {
639
+ const listenPort = port ?? this.opts.port ?? 0;
640
+ const listenHost = host ?? this.opts.host ?? "127.0.0.1";
641
+ this.server = createServer((req, res) => void this.handle(req, res));
642
+ await new Promise((resolve4) => this.server?.listen(listenPort, listenHost, () => resolve4()));
643
+ return this.server.address().port;
644
+ }
645
+ async stop() {
646
+ await new Promise(
647
+ (resolve4, reject) => this.server ? this.server.close((e) => e ? reject(e) : resolve4()) : resolve4()
648
+ );
649
+ this.server = void 0;
650
+ this.contexts.clear();
651
+ }
652
+ };
653
+
654
+ // src/client/index.ts
655
+ var AgentClient = class {
656
+ a2a;
657
+ fetchFn;
658
+ endpoint;
659
+ constructor(endpoint, opts = {}) {
660
+ this.endpoint = endpoint.replace(/\/$/, "");
661
+ this.fetchFn = opts.fetchFn ?? fetch;
662
+ this.a2a = new A2AClient(this.endpoint, opts);
663
+ }
664
+ async card() {
665
+ return this.a2a.getCard();
666
+ }
667
+ async send(text, contextId) {
668
+ return this.a2a.send(text, contextId);
669
+ }
670
+ /** Live StreamEvents over SSE (`POST /stream`). Throws AgentError on server errors. */
671
+ async *stream(text, contextId, signal, opts = {}) {
672
+ let res;
673
+ try {
674
+ res = await this.fetchFn(`${this.endpoint}/stream`, {
675
+ method: "POST",
676
+ headers: { "content-type": "application/json", accept: "text/event-stream" },
677
+ // JSON-RPC envelope — the server reads params.message.parts.
678
+ body: JSON.stringify({
679
+ jsonrpc: "2.0",
680
+ id: `a2a-${Date.now().toString(36)}`,
681
+ method: "message/stream",
682
+ params: {
683
+ message: { role: "user", parts: [{ kind: "text", text }] },
684
+ ...contextId ? { contextId } : {}
685
+ }
686
+ }),
687
+ ...signal ? { signal } : {}
688
+ });
689
+ } catch (err) {
690
+ throw new AgentError("A2A_TRANSPORT", `Stream failed: ${err instanceof Error ? err.message : String(err)}`, true);
691
+ }
692
+ if (!res.ok || !res.body) throw new AgentError("A2A_TRANSPORT", `Stream HTTP ${res.status}`, res.status >= 500);
693
+ const reader = res.body.getReader();
694
+ const decoder = new TextDecoder();
695
+ let buffer = "";
696
+ const flush = function* (chunk) {
697
+ buffer += chunk;
698
+ const lines = buffer.split("\n");
699
+ buffer = lines.pop() ?? "";
700
+ for (const line of lines) {
701
+ const t = line.trim();
702
+ if (!t.startsWith("data:")) continue;
703
+ const payload = t.slice(5).trim();
704
+ if (payload === "[DONE]") continue;
705
+ let msg;
706
+ try {
707
+ msg = JSON.parse(payload);
708
+ } catch {
709
+ continue;
710
+ }
711
+ if (msg.contextId) opts.onContext?.(msg.contextId);
712
+ if (msg.error) throw new AgentError("A2A_ERROR", msg.error, false);
713
+ if (msg.event && typeof msg.event.type === "string") yield msg.event;
714
+ }
715
+ };
716
+ try {
717
+ for (; ; ) {
718
+ const { done, value } = await reader.read();
719
+ if (done) break;
720
+ yield* flush(decoder.decode(value, { stream: true }));
721
+ }
722
+ yield* flush(decoder.decode());
723
+ } finally {
724
+ await reader.cancel().catch(() => {
725
+ });
726
+ }
727
+ }
728
+ };
729
+ async function handleAgentRoute(req, client) {
730
+ const { text, contextId } = await req.json();
731
+ let latestContextId = contextId;
732
+ const stream = new ReadableStream({
733
+ async start(controller) {
734
+ const send = (payload) => {
735
+ controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(payload)}
736
+
737
+ `));
738
+ };
739
+ try {
740
+ for await (const event of client.stream(text, contextId, req.signal, {
741
+ onContext: (id) => latestContextId = id
742
+ })) {
743
+ send({ contextId: latestContextId, event });
744
+ }
745
+ send({ contextId: latestContextId });
746
+ send("[DONE]");
747
+ } catch (err) {
748
+ send({ error: err instanceof Error ? err.message : String(err) });
749
+ } finally {
750
+ try {
751
+ controller.close();
752
+ } catch {
753
+ }
754
+ }
755
+ }
756
+ });
757
+ return new Response(stream, { headers: { "content-type": "text/event-stream", "cache-control": "no-cache" } });
758
+ }
759
+
760
+ // src/serve/index.ts
761
+ import { readFileSync as readFileSync2 } from "fs";
762
+ import { dirname as dirname2, resolve as resolve2 } from "path";
763
+ import { config as dotenvConfig } from "dotenv";
764
+ import { z as z5 } from "zod";
765
+
766
+ // src/models/capabilities.ts
767
+ var VISION_CAPABLE_PATTERNS = [
768
+ /qwen2.*vl/i,
769
+ /qwen.*vl/i,
770
+ /llama-3\.2-11b-vision/i,
771
+ /llama-3\.2-90b-vision/i,
772
+ /llava/i,
773
+ /minicpm-v/i,
774
+ /gpt-4o/i,
775
+ /gpt-4\.1/i,
776
+ /claude.*sonnet/i,
777
+ /claude.*opus/i,
778
+ /gemini/i
779
+ ];
780
+ function supportsVision(modelId) {
781
+ return VISION_CAPABLE_PATTERNS.some((re) => re.test(modelId));
782
+ }
783
+ function assertCanHandle(modelId, needsVision) {
784
+ if (needsVision && !supportsVision(modelId)) {
785
+ throw new AgentError(
786
+ "MODEL_CAPABILITY",
787
+ `Model "${modelId}" cannot take image input. Switch TOGETHER_MODEL to a vision-capable model (e.g. Qwen/Qwen2.5-VL-72B-Instruct) or drop the images.`,
788
+ false
789
+ );
790
+ }
791
+ }
792
+
793
+ // src/models/openai-compatible.ts
794
+ function reasoningOf(usage) {
795
+ return usage.reasoning_tokens ?? usage.completion_tokens_details?.reasoning_tokens;
796
+ }
797
+ function thinkingOf(m) {
798
+ const trace = m.reasoning ?? m.reasoning_content;
799
+ return trace ? trace : void 0;
800
+ }
801
+ function toWireRole(role) {
802
+ if (role === "tool") return "tool";
803
+ return role;
804
+ }
805
+ function toWireContent(m) {
806
+ if (typeof m.content === "string" && !hasImages(m.content)) return m.content;
807
+ const text = wireTextOf(m.content);
808
+ const imgs = imagesOf(m.content);
809
+ if (!imgs.length) return text;
810
+ return [
811
+ ...text ? [{ type: "text", text }] : [],
812
+ ...imgs.map((img) => ({
813
+ type: "image_url",
814
+ image_url: { url: img.data ? `data:${img.mimeType ?? "image/jpeg"};base64,${img.data}` : img.url }
815
+ }))
816
+ ];
817
+ }
818
+ function toWireMessage(m) {
819
+ const base = { role: toWireRole(m.role), content: toWireContent(m) };
820
+ if (m.toolCallId) base.tool_call_id = m.toolCallId;
821
+ if (typeof m.content !== "string") {
822
+ const calls = m.content.filter(
823
+ (b) => b.type === "tool_call"
824
+ );
825
+ if (calls.length) {
826
+ base.tool_calls = calls.map((c) => ({
827
+ id: c.id,
828
+ type: "function",
829
+ function: { name: c.name, arguments: JSON.stringify(c.args ?? {}) }
830
+ }));
831
+ }
832
+ }
833
+ return base;
834
+ }
835
+ var OpenAICompatibleProvider = class {
836
+ id;
837
+ capabilities = { structuredOutput: true, streaming: true };
838
+ opts;
839
+ constructor(opts) {
840
+ if (!opts.apiKey) throw new Error("OpenAICompatibleProvider: apiKey is required (.env TOGETHER_API_KEY / OPENAI_API_KEY)");
841
+ this.opts = { temperature: 0.2, ...opts };
842
+ this.id = `openai-compatible:${opts.model}`;
843
+ }
844
+ async complete(options) {
845
+ const { fetchFn = fetch, baseUrl, apiKey, model, temperature, maxTokens, stream } = this.opts;
846
+ const needsVision = options.messages.some((m) => hasImages(m.content));
847
+ assertCanHandle(model, needsVision);
848
+ const url = `${baseUrl.replace(/\/$/, "")}/chat/completions`;
849
+ const body = {
850
+ model,
851
+ temperature,
852
+ ...maxTokens ? { max_tokens: maxTokens } : {},
853
+ // stream_options makes OpenAI-compatible servers attach usage to the final
854
+ // chunk; without it streaming runs report {0,0} — budgets/cost would be fiction.
855
+ ...stream ? { stream: true, stream_options: { include_usage: true } } : {},
856
+ ...options.responseFormat ? {
857
+ response_format: {
858
+ type: "json_schema",
859
+ json_schema: {
860
+ name: options.responseFormat.name,
861
+ strict: options.responseFormat.strict ?? true,
862
+ schema: options.responseFormat.schema
863
+ }
864
+ }
865
+ } : {},
866
+ messages: options.messages.map(toWireMessage),
867
+ ...options.tools.length ? {
868
+ tools: options.tools.map((t) => ({
869
+ type: "function",
870
+ function: {
871
+ name: t.name,
872
+ description: t.description,
873
+ // Without parameters the API advertises a zero-arg tool.
874
+ ...t.parametersSchema ? { parameters: t.parametersSchema } : {}
875
+ }
876
+ }))
877
+ } : {}
878
+ };
879
+ let res;
880
+ try {
881
+ res = await fetchFn(url, {
882
+ method: "POST",
883
+ headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
884
+ body: JSON.stringify(body),
885
+ ...options.signal ? { signal: options.signal } : {}
886
+ });
887
+ } catch (err) {
888
+ throw new AgentError("MODEL_ERROR", `Model request failed: ${err instanceof Error ? err.message : String(err)}`, true);
889
+ }
890
+ if (!res.ok) {
891
+ const text = await res.text().catch(() => "");
892
+ const retryable = res.status === 429 || res.status >= 500;
893
+ throw new AgentError("MODEL_ERROR", `Model HTTP ${res.status}: ${text.slice(0, 300)}`, retryable);
894
+ }
895
+ return stream ? this.readStream(res, options) : this.readJson(res, options);
896
+ }
897
+ async readJson(res, options) {
898
+ const json = await res.json();
899
+ const choice = json.choices[0];
900
+ const msg = choice?.message ?? {};
901
+ const thinking = thinkingOf(msg);
902
+ if (thinking) options.onThinking?.(thinking);
903
+ if (msg.content) options.onToken?.(msg.content);
904
+ const finishReason = choice?.finish_reason ?? void 0;
905
+ if (!msg.content && !(msg.tool_calls ?? []).length && thinking && finishReason === "stop") {
906
+ throw new AgentError(
907
+ "MODEL_ERROR",
908
+ "Model stopped with finish_reason=stop having put the entire answer in the reasoning channel (empty content, no tool calls, thinking present). The reasoning/content split lost the reply \u2014 retry or raise maxTokens.",
909
+ true
910
+ );
911
+ }
912
+ if (!msg.content && !(msg.tool_calls ?? []).length && finishReason === "length") {
913
+ throw new AgentError(
914
+ "MODEL_ERROR",
915
+ `Model spent its entire max_tokens budget (${json.usage?.completion_tokens ?? "?"} tokens, finish_reason=length) thinking without producing content. Raise maxTokens.`,
916
+ false
917
+ );
918
+ }
919
+ const reasoningTokens = json.usage ? reasoningOf(json.usage) : void 0;
920
+ return {
921
+ text: msg.content ?? "",
922
+ ...thinking ? { thinking } : {},
923
+ toolCalls: parseToolCalls(msg.tool_calls ?? []),
924
+ ...finishReason ? { finishReason } : {},
925
+ usage: {
926
+ inputTokens: json.usage?.prompt_tokens ?? 0,
927
+ outputTokens: json.usage?.completion_tokens ?? 0,
928
+ ...reasoningTokens != null ? { reasoningTokens } : {}
929
+ }
930
+ };
931
+ }
932
+ /** SSE: `data: {...}` lines; delta.content -> onToken, delta.reasoning_content -> onThinking. */
933
+ async readStream(res, options) {
934
+ if (!res.body) throw new Error("Streaming response has no body");
935
+ const reader = res.body.getReader();
936
+ const decoder = new TextDecoder();
937
+ let text = "";
938
+ let thinking = "";
939
+ let inputTokens = 0;
940
+ let outputTokens = 0;
941
+ let reasoningTokens;
942
+ let finishReason;
943
+ const toolArgBuffers = /* @__PURE__ */ new Map();
944
+ let buffer = "";
945
+ const flush = (chunk) => {
946
+ buffer += chunk;
947
+ const lines = buffer.split("\n");
948
+ buffer = lines.pop() ?? "";
949
+ for (const line of lines) {
950
+ const t = line.trim();
951
+ if (!t.startsWith("data:")) continue;
952
+ const payload = t.slice(5).trim();
953
+ if (payload === "[DONE]") continue;
954
+ let evt;
955
+ try {
956
+ evt = JSON.parse(payload);
957
+ } catch {
958
+ continue;
959
+ }
960
+ if (evt.choices?.[0]?.finish_reason) finishReason = evt.choices[0].finish_reason ?? void 0;
961
+ if (evt.usage) {
962
+ if (evt.usage.prompt_tokens != null) inputTokens = evt.usage.prompt_tokens;
963
+ if (evt.usage.completion_tokens != null) outputTokens = evt.usage.completion_tokens;
964
+ const rt = reasoningOf(evt.usage);
965
+ if (rt != null) reasoningTokens = rt;
966
+ continue;
967
+ }
968
+ const delta = evt.choices?.[0]?.delta;
969
+ if (!delta) continue;
970
+ const think = thinkingOf(delta);
971
+ if (think) {
972
+ thinking += think;
973
+ options.onThinking?.(think);
974
+ }
975
+ if (delta.content) {
976
+ text += delta.content;
977
+ options.onToken?.(delta.content);
978
+ }
979
+ for (const [i, tc] of (delta.tool_calls ?? []).entries()) {
980
+ const key = tc.index ?? i;
981
+ const slot = toolArgBuffers.get(key) ?? { id: "", name: "", args: "" };
982
+ if (tc.id) slot.id = tc.id;
983
+ if (tc.function?.name) slot.name = tc.function.name;
984
+ if (tc.function?.arguments) slot.args += tc.function.arguments;
985
+ toolArgBuffers.set(key, slot);
986
+ }
987
+ }
988
+ };
989
+ for (; ; ) {
990
+ const { done, value } = await reader.read();
991
+ if (done) break;
992
+ flush(decoder.decode(value, { stream: true }));
993
+ }
994
+ flush(decoder.decode());
995
+ if (!text && !toolArgBuffers.size && thinking && finishReason === "stop") {
996
+ throw new AgentError(
997
+ "MODEL_ERROR",
998
+ "Stream ended at finish_reason=stop having put the entire answer in the reasoning channel (no content, no tool calls, thinking present). The reasoning/content split lost the reply \u2014 retry or raise maxTokens.",
999
+ true
1000
+ );
1001
+ }
1002
+ if (!text && !toolArgBuffers.size && finishReason === "length") {
1003
+ throw new AgentError(
1004
+ "MODEL_ERROR",
1005
+ `Stream ended at finish_reason=length with no content (${outputTokens} tokens burned). Raise maxTokens.`,
1006
+ false
1007
+ );
1008
+ }
1009
+ return {
1010
+ text,
1011
+ ...thinking ? { thinking } : {},
1012
+ ...finishReason ? { finishReason } : {},
1013
+ toolCalls: [...toolArgBuffers.values()].filter((t) => t.name).map((t) => ({ id: t.id || `call_${++callSeq}`, name: t.name, args: safeJson(t.args) })),
1014
+ usage: {
1015
+ inputTokens,
1016
+ outputTokens,
1017
+ ...reasoningTokens != null ? { reasoningTokens } : {}
1018
+ }
1019
+ };
1020
+ }
1021
+ };
1022
+ var callSeq = 0;
1023
+ function parseToolCalls(calls) {
1024
+ return calls.map((c) => ({ id: c.id ?? `call_${++callSeq}`, name: c.function.name, args: safeJson(c.function.arguments || "{}") }));
1025
+ }
1026
+ function safeJson(raw) {
1027
+ try {
1028
+ return JSON.parse(raw || "{}");
1029
+ } catch {
1030
+ return {};
1031
+ }
1032
+ }
1033
+ function providerFromEnv(active, extra) {
1034
+ return new OpenAICompatibleProvider({ baseUrl: active.baseUrl, apiKey: active.apiKey, model: active.model, ...extra });
1035
+ }
1036
+
1037
+ // src/models/anthropic.ts
1038
+ function normalizeStopReason(stopReason) {
1039
+ if (!stopReason) return void 0;
1040
+ return stopReason === "max_tokens" ? "length" : stopReason;
1041
+ }
1042
+ function assertNotEmptyTruncation(text, toolCalls, finishReason, tokenCount) {
1043
+ if (!text && !toolCalls.length && finishReason === "length") {
1044
+ throw new AgentError(
1045
+ "MODEL_ERROR",
1046
+ `Model stopped at max_tokens (${tokenCount} tokens) without producing content. Raise maxTokens.`,
1047
+ false
1048
+ );
1049
+ }
1050
+ }
1051
+ function assertNotEmptyReasoningSplit(text, toolCalls, thinking, stopReason) {
1052
+ if (!text && !toolCalls.length && thinking && stopReason === "stop") {
1053
+ throw new AgentError(
1054
+ "MODEL_ERROR",
1055
+ "Model stopped with stop_reason=stop having put the entire answer in the reasoning channel (empty content, no tool calls, thinking present). The reasoning/content split lost the reply \u2014 retry or raise maxTokens.",
1056
+ true
1057
+ );
1058
+ }
1059
+ }
1060
+ function toAnthropicContent(m) {
1061
+ if (typeof m.content === "string") return m.content;
1062
+ const parts = [];
1063
+ const text = wireTextOf(m.content);
1064
+ if (text) parts.push({ type: "text", text });
1065
+ for (const img of imagesOf(m.content)) {
1066
+ parts.push({
1067
+ type: "image",
1068
+ source: img.data ? { type: "base64", media_type: img.mimeType ?? "image/jpeg", data: img.data } : { type: "url", url: img.url }
1069
+ });
1070
+ }
1071
+ for (const b of m.content) {
1072
+ if (b.type === "tool_call") parts.push({ type: "tool_use", id: b.id, name: b.name, input: b.args ?? {} });
1073
+ }
1074
+ return parts;
1075
+ }
1076
+ var AnthropicProvider = class {
1077
+ id;
1078
+ capabilities = { structuredOutput: false, streaming: true };
1079
+ opts;
1080
+ constructor(opts) {
1081
+ if (!opts.apiKey) throw new Error("AnthropicProvider: apiKey is required (.env ANTHROPIC_API_KEY)");
1082
+ this.opts = { baseUrl: "https://api.anthropic.com", maxTokens: 1024, ...opts };
1083
+ if (this.opts.thinkingBudget && this.opts.maxTokens != null && this.opts.maxTokens <= this.opts.thinkingBudget) {
1084
+ throw new Error(
1085
+ `AnthropicProvider: maxTokens (${this.opts.maxTokens}) must exceed thinkingBudget (${this.opts.thinkingBudget}); raise maxTokens`
1086
+ );
1087
+ }
1088
+ this.id = `anthropic:${opts.model}`;
1089
+ }
1090
+ async complete(options) {
1091
+ const { fetchFn = fetch, apiKey, model, baseUrl, maxTokens, temperature, thinkingBudget, stream } = this.opts;
1092
+ const needsVision = options.messages.some((m) => hasImages(m.content));
1093
+ assertCanHandle(model, needsVision);
1094
+ const system2 = options.messages.filter((m) => m.role === "system").map((m) => wireTextOf(m.content)).join("\n\n");
1095
+ const messages = [];
1096
+ for (const m of options.messages.filter((x) => x.role !== "system")) {
1097
+ if (m.role === "tool") {
1098
+ const result = { type: "tool_result", tool_use_id: m.toolCallId, content: toAnthropicContent(m) };
1099
+ const last = messages[messages.length - 1];
1100
+ if (last && last.role === "user" && Array.isArray(last.content) && last.content.some((b) => b.type === "tool_result")) {
1101
+ last.content.push(result);
1102
+ continue;
1103
+ }
1104
+ messages.push({ role: "user", content: [result] });
1105
+ continue;
1106
+ }
1107
+ messages.push({ role: m.role === "assistant" ? "assistant" : "user", content: toAnthropicContent(m) });
1108
+ }
1109
+ const body = {
1110
+ model,
1111
+ max_tokens: maxTokens,
1112
+ ...system2 ? { system: system2 } : {},
1113
+ ...temperature != null ? { temperature } : {},
1114
+ ...thinkingBudget ? { thinking: { type: "enabled", budget_tokens: thinkingBudget } } : {},
1115
+ ...stream ? { stream: true } : {},
1116
+ messages,
1117
+ ...options.tools.length ? {
1118
+ tools: options.tools.map((t) => ({
1119
+ name: t.name,
1120
+ description: t.description,
1121
+ input_schema: t.parametersSchema ?? { type: "object" }
1122
+ }))
1123
+ } : {}
1124
+ };
1125
+ let res;
1126
+ try {
1127
+ res = await fetchFn(`${baseUrl.replace(/\/$/, "")}/v1/messages`, {
1128
+ method: "POST",
1129
+ headers: {
1130
+ "content-type": "application/json",
1131
+ "x-api-key": apiKey,
1132
+ "anthropic-version": "2023-06-01",
1133
+ ...stream ? { accept: "text/event-stream" } : {}
1134
+ },
1135
+ body: JSON.stringify(body),
1136
+ ...options.signal ? { signal: options.signal } : {}
1137
+ });
1138
+ } catch (err) {
1139
+ throw new AgentError("MODEL_ERROR", `Model request failed: ${err instanceof Error ? err.message : String(err)}`, true);
1140
+ }
1141
+ if (!res.ok) {
1142
+ const text = await res.text().catch(() => "");
1143
+ const retryable = res.status === 429 || res.status >= 500;
1144
+ throw new AgentError("MODEL_ERROR", `Model HTTP ${res.status}: ${text.slice(0, 300)}`, retryable);
1145
+ }
1146
+ return stream ? this.readStream(res, options) : this.readJson(res, options);
1147
+ }
1148
+ toCompletion(blocks, usage, options) {
1149
+ let text = "";
1150
+ let thinking = "";
1151
+ const toolCalls = [];
1152
+ for (const b of blocks) {
1153
+ if (b.type === "text" && b.text) {
1154
+ text += b.text;
1155
+ options.onToken?.(b.text);
1156
+ } else if (b.type === "thinking" && b.thinking) {
1157
+ thinking += b.thinking;
1158
+ options.onThinking?.(b.thinking);
1159
+ } else if (b.type === "tool_use" && b.name) {
1160
+ toolCalls.push({ id: b.id ?? `call_${toolCalls.length}`, name: b.name, args: b.input ?? {} });
1161
+ }
1162
+ }
1163
+ return { text, ...thinking ? { thinking } : {}, toolCalls, usage };
1164
+ }
1165
+ async readJson(res, options) {
1166
+ const json = await res.json();
1167
+ const out = this.toCompletion(json.content ?? [], {
1168
+ inputTokens: json.usage?.input_tokens ?? 0,
1169
+ outputTokens: json.usage?.output_tokens ?? 0
1170
+ }, options);
1171
+ const finishReason = normalizeStopReason(json.stop_reason);
1172
+ assertNotEmptyTruncation(out.text, out.toolCalls, finishReason, json.usage?.output_tokens ?? 0);
1173
+ assertNotEmptyReasoningSplit(out.text, out.toolCalls, out.thinking ?? "", finishReason);
1174
+ return finishReason ? { ...out, finishReason } : out;
1175
+ }
1176
+ async readStream(res, options) {
1177
+ if (!res.body) throw new Error("Streaming response has no body");
1178
+ const reader = res.body.getReader();
1179
+ const decoder = new TextDecoder();
1180
+ let buffer = "";
1181
+ let event = "";
1182
+ let inputTokens = 0;
1183
+ let outputTokens = 0;
1184
+ let stopReason;
1185
+ const blocks = [];
1186
+ let current;
1187
+ let jsonInput = "";
1188
+ const flush = (chunk) => {
1189
+ buffer += chunk;
1190
+ const lines = buffer.split("\n");
1191
+ buffer = lines.pop() ?? "";
1192
+ for (const line of lines) {
1193
+ const t = line.trim();
1194
+ if (!t) continue;
1195
+ if (t.startsWith("event:")) {
1196
+ event = t.slice(6).trim();
1197
+ continue;
1198
+ }
1199
+ if (!t.startsWith("data:")) continue;
1200
+ const payload = t.slice(5).trim();
1201
+ if (!payload) continue;
1202
+ let data;
1203
+ try {
1204
+ data = JSON.parse(payload);
1205
+ } catch {
1206
+ continue;
1207
+ }
1208
+ if (event === "message_start") {
1209
+ const mu = (data.message ?? {}).usage;
1210
+ if (mu?.input_tokens != null) inputTokens = mu.input_tokens;
1211
+ } else if (event === "message_delta") {
1212
+ const du = data.usage ?? {};
1213
+ if (du.output_tokens != null) outputTokens = du.output_tokens;
1214
+ const sr = data.delta ?? {};
1215
+ if (sr.stop_reason) stopReason = normalizeStopReason(sr.stop_reason);
1216
+ } else if (event === "content_block_start") {
1217
+ const block = data.content_block ?? {};
1218
+ current = { type: block.type, id: block.id, name: block.name };
1219
+ jsonInput = "";
1220
+ } else if (event === "content_block_delta") {
1221
+ const delta = data.delta ?? {};
1222
+ if (delta.text) {
1223
+ current = current ?? { type: "text" };
1224
+ current.text = (current.text ?? "") + delta.text;
1225
+ } else if (delta.thinking) {
1226
+ current = current ?? { type: "thinking" };
1227
+ current.thinking = (current.thinking ?? "") + delta.thinking;
1228
+ } else if (delta.partial_json) {
1229
+ jsonInput += delta.partial_json;
1230
+ }
1231
+ } else if (event === "content_block_stop") {
1232
+ if (current) {
1233
+ if (current.type === "tool_use") {
1234
+ try {
1235
+ current.input = JSON.parse(jsonInput || "{}");
1236
+ } catch {
1237
+ current.input = {};
1238
+ }
1239
+ }
1240
+ blocks.push(current);
1241
+ current = void 0;
1242
+ }
1243
+ }
1244
+ }
1245
+ };
1246
+ for (; ; ) {
1247
+ const { done, value } = await reader.read();
1248
+ if (done) break;
1249
+ flush(decoder.decode(value, { stream: true }));
1250
+ }
1251
+ flush(decoder.decode());
1252
+ if (current) blocks.push(current);
1253
+ const out = this.toCompletion(blocks, { inputTokens, outputTokens }, options);
1254
+ assertNotEmptyTruncation(out.text, out.toolCalls, stopReason, outputTokens);
1255
+ assertNotEmptyReasoningSplit(out.text, out.toolCalls, out.thinking ?? "", stopReason);
1256
+ return stopReason ? { ...out, finishReason: stopReason } : out;
1257
+ }
1258
+ };
1259
+
1260
+ // src/models/ollama.ts
1261
+ var OllamaProvider = class extends OpenAICompatibleProvider {
1262
+ id;
1263
+ capabilities = { structuredOutput: false, streaming: true };
1264
+ constructor(opts) {
1265
+ super({
1266
+ baseUrl: opts.baseUrl ?? "http://localhost:11434/v1",
1267
+ apiKey: "ollama",
1268
+ model: opts.model,
1269
+ ...opts.temperature != null ? { temperature: opts.temperature } : {},
1270
+ ...opts.maxTokens != null ? { maxTokens: opts.maxTokens } : {},
1271
+ ...opts.stream != null ? { stream: opts.stream } : {},
1272
+ ...opts.fetchFn ? { fetchFn: opts.fetchFn } : {}
1273
+ });
1274
+ this.id = `ollama:${opts.model}`;
1275
+ }
1276
+ };
1277
+ function ollama(opts) {
1278
+ return new OllamaProvider(opts);
1279
+ }
1280
+
1281
+ // src/models/factory.ts
1282
+ function initChatModel(spec, opts = {}) {
1283
+ const cfg = loadConfig();
1284
+ const extra = {
1285
+ ...opts.temperature != null ? { temperature: opts.temperature } : {},
1286
+ ...opts.maxTokens != null ? { maxTokens: opts.maxTokens } : {},
1287
+ ...opts.stream != null ? { stream: opts.stream } : {}
1288
+ };
1289
+ const colon = spec.indexOf(":");
1290
+ const provider = colon > 0 ? spec.slice(0, colon) : "together";
1291
+ const model = colon > 0 ? spec.slice(colon + 1) : spec;
1292
+ switch (provider) {
1293
+ case "together": {
1294
+ const apiKey = opts.apiKey ?? cfg.TOGETHER_API_KEY;
1295
+ if (!apiKey) throw new Error("initChatModel: set TOGETHER_API_KEY in .env (or pass apiKey)");
1296
+ return new OpenAICompatibleProvider({ baseUrl: opts.baseUrl ?? cfg.TOGETHER_BASE_URL, apiKey, model, ...extra });
1297
+ }
1298
+ case "openai": {
1299
+ const apiKey = opts.apiKey ?? cfg.OPENAI_API_KEY;
1300
+ if (!apiKey) throw new Error("initChatModel: set OPENAI_API_KEY in .env (or pass apiKey)");
1301
+ return new OpenAICompatibleProvider({ baseUrl: opts.baseUrl ?? cfg.OPENAI_BASE_URL, apiKey, model, ...extra });
1302
+ }
1303
+ case "anthropic": {
1304
+ const apiKey = opts.apiKey ?? cfg.ANTHROPIC_API_KEY;
1305
+ if (!apiKey) throw new Error("initChatModel: set ANTHROPIC_API_KEY in .env (or pass apiKey)");
1306
+ return new AnthropicProvider({
1307
+ apiKey,
1308
+ model,
1309
+ ...opts.baseUrl ? { baseUrl: opts.baseUrl } : { baseUrl: cfg.ANTHROPIC_BASE_URL },
1310
+ ...opts.thinkingBudget != null ? { thinkingBudget: opts.thinkingBudget } : {},
1311
+ ...extra
1312
+ });
1313
+ }
1314
+ case "ollama": {
1315
+ return new OllamaProvider({ model, ...opts.baseUrl ? { baseUrl: opts.baseUrl } : { baseUrl: cfg.OLLAMA_BASE_URL }, ...extra });
1316
+ }
1317
+ case "custom": {
1318
+ if (!opts.baseUrl || !opts.apiKey) throw new Error("initChatModel: custom: needs baseUrl + apiKey");
1319
+ return new OpenAICompatibleProvider({ baseUrl: opts.baseUrl, apiKey: opts.apiKey, model, ...extra });
1320
+ }
1321
+ default:
1322
+ throw new Error(`initChatModel: unknown provider "${provider}" (use together:|openai:|anthropic:|ollama:|custom:)`);
1323
+ }
1324
+ }
1325
+
1326
+ // src/skills/index.ts
1327
+ import { readdirSync, readFileSync, statSync } from "fs";
1328
+ import { join as join3 } from "path";
1329
+ import { z as z4 } from "zod";
1330
+ var FrontmatterSchema = z4.object({
1331
+ name: z4.string().min(1),
1332
+ description: z4.string().min(1),
1333
+ version: z4.string().default("0.1.0")
1334
+ });
1335
+ function parseSkillFile(content, path = "<inline>") {
1336
+ const raw = content.startsWith("\uFEFF") ? content.slice(1) : content;
1337
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
1338
+ if (!match) throw new Error(`Invalid SKILL.md (missing frontmatter): ${path}`);
1339
+ const [, fm, body] = match;
1340
+ const meta = {};
1341
+ for (const line of fm.split("\n")) {
1342
+ const i = line.indexOf(":");
1343
+ if (i > 0) meta[line.slice(0, i).trim()] = line.slice(i + 1).trim().replace(/^["']|["']$/g, "");
1344
+ }
1345
+ const parsed = FrontmatterSchema.parse(meta);
1346
+ if (!body.trim()) throw new Error(`Invalid SKILL.md (empty body): ${path}`);
1347
+ return { ...parsed, path, body: body.trim() };
1348
+ }
1349
+ var SkillRegistry = class {
1350
+ skills = /* @__PURE__ */ new Map();
1351
+ register(skill) {
1352
+ if (this.skills.has(skill.name)) throw new Error(`Skill already registered: ${skill.name}`);
1353
+ this.skills.set(skill.name, skill);
1354
+ return this;
1355
+ }
1356
+ get(name) {
1357
+ return this.skills.get(name);
1358
+ }
1359
+ list() {
1360
+ return [...this.skills.values()];
1361
+ }
1362
+ /** Expose each skill's tools (if any) for Agent consumption. */
1363
+ asTools() {
1364
+ return this.list().flatMap((s) => s.tools ?? []);
1365
+ }
1366
+ };
1367
+ async function discoverSkills(dirs, opts = {}) {
1368
+ const found = [];
1369
+ for (const dir of dirs) {
1370
+ let entries;
1371
+ try {
1372
+ entries = readdirSync(dir);
1373
+ } catch {
1374
+ continue;
1375
+ }
1376
+ for (const entry of entries) {
1377
+ const full = join3(dir, entry);
1378
+ let stat3;
1379
+ try {
1380
+ stat3 = statSync(full);
1381
+ } catch {
1382
+ continue;
1383
+ }
1384
+ const file = stat3.isDirectory() ? join3(full, "SKILL.md") : full;
1385
+ if (!file.endsWith("SKILL.md")) continue;
1386
+ try {
1387
+ found.push({ ...parseSkillFile(readFileSync(file, "utf8"), file), tools: [] });
1388
+ } catch (err) {
1389
+ if (opts.strict) throw err;
1390
+ }
1391
+ }
1392
+ }
1393
+ return found;
1394
+ }
1395
+ async function loadSkillsInto(registry, dirs, opts = {}) {
1396
+ const skills = await discoverSkills(dirs, opts);
1397
+ for (const s of skills) registry.register(s);
1398
+ return skills;
1399
+ }
1400
+ function tokenize(text) {
1401
+ return text.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 1);
1402
+ }
1403
+ function scoreSkills(registry, input) {
1404
+ const skills = registry.list();
1405
+ const docs = skills.map((s) => tokenize(`${s.name} ${s.description}`));
1406
+ const query = tokenize(input);
1407
+ const df = /* @__PURE__ */ new Map();
1408
+ for (const doc of docs) {
1409
+ for (const term of new Set(doc)) df.set(term, (df.get(term) ?? 0) + 1);
1410
+ }
1411
+ const idf = (term) => Math.log((docs.length + 1) / ((df.get(term) ?? 0) + 1)) + 1;
1412
+ const qtf = /* @__PURE__ */ new Map();
1413
+ for (const t of query) qtf.set(t, (qtf.get(t) ?? 0) + 1);
1414
+ const qnorm = Math.sqrt([...qtf.values()].reduce((n, c) => n + c * c, 0)) || 1;
1415
+ return skills.map((skill, i) => {
1416
+ if (new RegExp(`\\$${skill.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w-])`).test(input)) {
1417
+ return { skill, score: Number.POSITIVE_INFINITY };
1418
+ }
1419
+ const dtf = /* @__PURE__ */ new Map();
1420
+ for (const t of docs[i]) dtf.set(t, (dtf.get(t) ?? 0) + 1);
1421
+ let dot = 0;
1422
+ let dnorm = 0;
1423
+ for (const [term, count] of dtf) {
1424
+ const w = count * idf(term);
1425
+ dnorm += w * w;
1426
+ if (qtf.has(term)) dot += w * (qtf.get(term) ?? 0) * idf(term);
1427
+ }
1428
+ const denom = Math.sqrt(dnorm) * qnorm || 1;
1429
+ return { skill, score: dot / denom };
1430
+ });
1431
+ }
1432
+ function matchSkills(registry, input, threshold = 0.05) {
1433
+ return scoreSkills(registry, input).filter(({ score }) => score >= threshold).sort((a, b) => b.score - a.score).map(({ skill }) => skill);
1434
+ }
1435
+
1436
+ // src/serve/index.ts
1437
+ var AgentDefSchema = z5.object({
1438
+ name: z5.string().min(1),
1439
+ description: z5.string().default(""),
1440
+ systemPrompt: z5.string().default("You are a helpful agent. Use tools when needed. Be concise."),
1441
+ model: z5.string(),
1442
+ port: z5.number().int().positive().optional(),
1443
+ skills: z5.array(z5.string()).default([]),
1444
+ filesystem: z5.string().optional(),
1445
+ todos: z5.boolean().default(false),
1446
+ maxSteps: z5.number().int().positive().default(8),
1447
+ sessionDir: z5.string().optional(),
1448
+ /** Extra env file for this agent (keys override framework-level). */
1449
+ envFile: z5.string().optional()
1450
+ });
1451
+ var AgentEntrySchema = z5.union([
1452
+ AgentDefSchema,
1453
+ z5.object({ file: z5.string() }).catchall(z5.unknown())
1454
+ ]);
1455
+ var FrameworkFileSchema = z5.object({
1456
+ server: z5.object({ host: z5.string().default("127.0.0.1"), basePort: z5.number().int().min(0).default(8484) }).default({}),
1457
+ /** Framework-level env file(s), loaded before agents resolve models. */
1458
+ envFile: z5.union([z5.string(), z5.array(z5.string())]).optional(),
1459
+ /** Inline env mapping (applied after env files, overrides them). */
1460
+ env: z5.record(z5.string()).optional(),
1461
+ /** Local checkpoint dir for served agents (durable HITL threads). */
1462
+ checkpointer: z5.object({ dir: z5.string() }).optional(),
1463
+ http: z5.object({ corsOrigins: z5.array(z5.string()).default([]) }).default({}),
1464
+ agents: z5.array(AgentEntrySchema).min(1)
1465
+ });
1466
+ function loadEnvFile(path, override = true) {
1467
+ dotenvConfig({ path, override });
1468
+ }
1469
+ function resolveAgentPaths(def, dir) {
1470
+ return {
1471
+ ...def,
1472
+ skills: def.skills.map((s) => resolve2(dir, s)),
1473
+ ...def.filesystem ? { filesystem: resolve2(dir, def.filesystem) } : {},
1474
+ ...def.sessionDir ? { sessionDir: resolve2(dir, def.sessionDir) } : {},
1475
+ ...def.envFile ? { envFile: resolve2(dir, def.envFile) } : {}
1476
+ };
1477
+ }
1478
+ function resolveAgentEntry(fileRaw, overrides, agentDir, frameworkDir) {
1479
+ const fileKeys = new Set(Object.keys(fileRaw));
1480
+ const merged = AgentDefSchema.parse({ ...fileRaw, ...overrides });
1481
+ const against = (key) => fileKeys.has(key) && !(key in overrides) ? agentDir : frameworkDir;
1482
+ return {
1483
+ ...merged,
1484
+ skills: merged.skills.map((s) => resolve2(against("skills"), s)),
1485
+ ...merged.filesystem ? { filesystem: resolve2(against("filesystem"), merged.filesystem) } : {},
1486
+ ...merged.sessionDir ? { sessionDir: resolve2(against("sessionDir"), merged.sessionDir) } : {},
1487
+ ...merged.envFile ? { envFile: resolve2(against("envFile"), merged.envFile) } : {}
1488
+ };
1489
+ }
1490
+ function loadFrameworkConfig(path) {
1491
+ const raw = JSON.parse(readFileSync2(path, "utf8"));
1492
+ const parsed = FrameworkFileSchema.parse(raw);
1493
+ const dir = dirname2(resolve2(path));
1494
+ for (const envFile of parsed.envFile ? [parsed.envFile].flat() : []) {
1495
+ loadEnvFile(resolve2(dir, envFile), false);
1496
+ }
1497
+ for (const [key, value] of Object.entries(parsed.env ?? {})) {
1498
+ process.env[key] = value;
1499
+ }
1500
+ return {
1501
+ server: parsed.server,
1502
+ ...parsed.envFile ? { envFile: parsed.envFile } : {},
1503
+ ...parsed.env ? { env: parsed.env } : {},
1504
+ ...parsed.checkpointer ? { checkpointer: { dir: resolve2(dir, parsed.checkpointer.dir) } } : {},
1505
+ http: parsed.http,
1506
+ agents: parsed.agents.map((entry) => {
1507
+ if ("file" in entry && typeof entry.file === "string") {
1508
+ const agentPath = resolve2(dir, entry.file);
1509
+ const agentDir = dirname2(agentPath);
1510
+ const fileRaw = JSON.parse(readFileSync2(agentPath, "utf8"));
1511
+ const { file: _file, ...overrides } = entry;
1512
+ void _file;
1513
+ const resolved2 = resolveAgentEntry(fileRaw, overrides, agentDir, dir);
1514
+ if (resolved2.envFile) loadEnvFile(resolved2.envFile);
1515
+ return resolved2;
1516
+ }
1517
+ const resolved = resolveAgentPaths(AgentDefSchema.parse(entry), dir);
1518
+ if (resolved.envFile) loadEnvFile(resolved.envFile);
1519
+ return resolved;
1520
+ })
1521
+ };
1522
+ }
1523
+ async function buildServedAgent(def, opts = {}) {
1524
+ const middlewares = [];
1525
+ if (def.filesystem) middlewares.push(filesystemMiddleware({ root: def.filesystem }));
1526
+ if (def.todos) middlewares.push(todoListMiddleware());
1527
+ let systemPrompt = def.systemPrompt;
1528
+ if (def.skills.length) {
1529
+ const registry = new SkillRegistry();
1530
+ for (const dir of def.skills) {
1531
+ for (const s of await discoverSkills([dir])) {
1532
+ try {
1533
+ registry.register(s);
1534
+ } catch {
1535
+ }
1536
+ }
1537
+ }
1538
+ const catalog = registry.list().map((s) => `- ${s.name}: ${s.description}`).join("\n");
1539
+ if (catalog) systemPrompt += `
1540
+
1541
+ Available skill packs (mention $name to invoke):
1542
+ ${catalog}`;
1543
+ }
1544
+ const agent = new Agent({
1545
+ name: def.name,
1546
+ description: def.description,
1547
+ systemPrompt,
1548
+ model: initChatModel(def.model),
1549
+ maxSteps: def.maxSteps,
1550
+ middleware: middlewares,
1551
+ ...def.sessionDir ? {
1552
+ // Context-scoped id keeps one def's sessions from mingling when
1553
+ // the server runs a fresh agent per A2A contextId.
1554
+ sessionManager: new SessionManager({
1555
+ sessionId: opts.sessionId ?? def.name,
1556
+ storage: new LocalFileStorage(def.sessionDir)
1557
+ })
1558
+ } : {}
1559
+ });
1560
+ return { agent, card: { name: def.name, ...def.description ? { description: def.description } : {} } };
1561
+ }
1562
+ async function serveFramework(file) {
1563
+ const served = [];
1564
+ const checkpointer = file.checkpointer ? new FileCheckpointer(file.checkpointer.dir) : void 0;
1565
+ let port = file.server.basePort;
1566
+ for (const def of file.agents) {
1567
+ const { card } = await buildServedAgent(def);
1568
+ const server = new A2AServer({
1569
+ card,
1570
+ // Fresh agent per A2A contextId — sharing one instance would leak one
1571
+ // conversation into another context's history.
1572
+ agentFactory: (contextId) => buildServedAgent(def, def.sessionDir ? { sessionId: `${def.name}:${contextId}` } : {}).then((r) => r.agent),
1573
+ ...checkpointer ? { checkpointer } : {},
1574
+ corsOrigins: file.http.corsOrigins,
1575
+ host: file.server.host
1576
+ });
1577
+ const actual = await server.start(def.port ?? port, file.server.host);
1578
+ port = actual + 1;
1579
+ const url = `http://${file.server.host}:${actual}`;
1580
+ served.push({ name: def.name, url, cardUrl: `${url}/.well-known/agent-card.json`, server });
1581
+ }
1582
+ return {
1583
+ agents: served,
1584
+ stop: async () => {
1585
+ for (const s of served) await s.server.stop();
1586
+ }
1587
+ };
1588
+ }
1589
+
1590
+ // src/rag/index.ts
1591
+ import { z as z6 } from "zod";
1592
+ var InMemoryRetriever = class {
1593
+ id = "tfidf";
1594
+ docs;
1595
+ df = /* @__PURE__ */ new Map();
1596
+ constructor(docs) {
1597
+ this.docs = docs.map((d) => {
1598
+ const terms = /* @__PURE__ */ new Map();
1599
+ for (const t of tokenize(d.text)) terms.set(t, (terms.get(t) ?? 0) + 1);
1600
+ return { ...d, terms };
1601
+ });
1602
+ for (const d of this.docs) {
1603
+ for (const t of new Set(d.terms.keys())) this.df.set(t, (this.df.get(t) ?? 0) + 1);
1604
+ }
1605
+ }
1606
+ idf(term) {
1607
+ return Math.log((this.docs.length + 1) / ((this.df.get(term) ?? 0) + 1)) + 1;
1608
+ }
1609
+ async search(query, k = 4) {
1610
+ const qtf = /* @__PURE__ */ new Map();
1611
+ for (const t of tokenize(query)) qtf.set(t, (qtf.get(t) ?? 0) + 1);
1612
+ return this.docs.map((d) => {
1613
+ let dot = 0;
1614
+ let norm = 0;
1615
+ for (const [term, count] of d.terms) {
1616
+ const w = count * this.idf(term);
1617
+ norm += w * w;
1618
+ if (qtf.has(term)) dot += w * (qtf.get(term) ?? 0) * this.idf(term);
1619
+ }
1620
+ const qnorm = Math.sqrt([...qtf.values()].reduce((n, c) => n + c * c, 0)) || 1;
1621
+ return { id: d.id, text: d.text, meta: d.meta, score: dot / ((Math.sqrt(norm) || 1) * qnorm) };
1622
+ }).filter((d) => d.score > 0).sort((a, b) => b.score - a.score).slice(0, k);
1623
+ }
1624
+ };
1625
+ function retrievalTool(retriever, opts = {}) {
1626
+ return defineTool({
1627
+ name: opts.name ?? "retrieve",
1628
+ description: `Search the knowledge base (${retriever.id}). Returns top passages with scores.`,
1629
+ schema: z6.object({ query: z6.string().min(1).max(500), k: z6.number().int().min(1).max(10).default(opts.k ?? 4) }),
1630
+ execute: async ({ query, k }) => retriever.search(query, k)
1631
+ });
1632
+ }
1633
+ function sparqlRetriever(store, opts = {}) {
1634
+ return {
1635
+ id: opts.id ?? "sparql",
1636
+ search: async (query, k = 4) => {
1637
+ const terms = new Set(tokenize(query));
1638
+ if (!terms.size) return [];
1639
+ const quads = await store.match(null, null, null);
1640
+ return quads.map((q, i) => {
1641
+ const hay = `${q.subject} ${q.predicate} ${q.object}`.toLowerCase();
1642
+ let score = 0;
1643
+ for (const t of terms) if (hay.includes(t)) score += 1;
1644
+ return { id: `q${i}`, text: `${q.subject} ${q.predicate} ${q.object}`, score };
1645
+ }).filter((d) => d.score > 0).sort((a, b) => b.score - a.score).slice(0, k);
1646
+ }
1647
+ };
1648
+ }
1649
+
1650
+ // src/sandbox/index.ts
1651
+ import { runInNewContext } from "vm";
1652
+ import { z as z7 } from "zod";
1653
+ import { execFile } from "child_process";
1654
+ import { readdir, readFile as readFile2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
1655
+ import { join as join4, relative, resolve as resolve3 } from "path";
1656
+ function runJavascriptTool(opts = {}) {
1657
+ const timeoutMs = opts.timeoutMs ?? 2e3;
1658
+ const maxOutputChars = opts.maxOutputChars ?? 4e3;
1659
+ return defineTool({
1660
+ name: "run_javascript",
1661
+ description: "Execute JavaScript in a sandbox (no network, no fs, timeout). Completion value becomes the result.",
1662
+ schema: z7.object({ code: z7.string().min(1).max(8e3) }),
1663
+ execute: async ({ code }) => {
1664
+ let result;
1665
+ try {
1666
+ result = runInNewContext(code, Object.freeze({ Math, JSON }), { timeout: timeoutMs });
1667
+ } catch (err) {
1668
+ const message = err instanceof Error ? err.message : String(err);
1669
+ if (/timed out/i.test(message)) {
1670
+ throw new AgentError("SANDBOX_TIMEOUT", `Sandbox exceeded ${timeoutMs}ms`, true);
1671
+ }
1672
+ throw new AgentError("SANDBOX_ERROR", `Sandbox failed: ${message}`, false);
1673
+ }
1674
+ const text = typeof result === "string" ? result : JSON.stringify(result ?? null);
1675
+ return text.length > maxOutputChars ? `${text.slice(0, maxOutputChars)}\u2026[truncated]` : result ?? null;
1676
+ }
1677
+ });
1678
+ }
1679
+ var SCRUBBED_ENV = ["PATH", "HOME", "LANG", "LC_ALL", "TZ", "TERM", "NO_COLOR"];
1680
+ function baseEnv(extra = {}) {
1681
+ const env = {};
1682
+ for (const k of SCRUBBED_ENV) {
1683
+ const v = process.env[k];
1684
+ if (v !== void 0) env[k] = v;
1685
+ }
1686
+ return { ...env, ...extra };
1687
+ }
1688
+ function escapeRegExp(s) {
1689
+ return s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
1690
+ }
1691
+ function matchGlob(pattern, path) {
1692
+ const parts = pattern.split("**/");
1693
+ let rx = "^";
1694
+ parts.forEach((part, index) => {
1695
+ if (index > 0) rx += "(.*/)?";
1696
+ for (let i = 0; i < part.length; i++) {
1697
+ const c = part[i];
1698
+ if (c === "*" && part[i + 1] === "*") {
1699
+ rx += ".*";
1700
+ i += 1;
1701
+ } else if (c === "*") rx += "[^/]*";
1702
+ else if (c === "?") rx += "[^/]";
1703
+ else rx += escapeRegExp(c);
1704
+ }
1705
+ });
1706
+ return new RegExp(`${rx}$`).test(path);
1707
+ }
1708
+ async function walkFiles(root) {
1709
+ const out = [];
1710
+ const walk = async (dir) => {
1711
+ let entries;
1712
+ try {
1713
+ entries = await readdir(dir, { withFileTypes: true });
1714
+ } catch {
1715
+ return;
1716
+ }
1717
+ for (const e of entries) {
1718
+ const full = join4(dir, e.name);
1719
+ if (e.isDirectory()) await walk(full);
1720
+ else if (e.isFile()) out.push(relative(root, full));
1721
+ }
1722
+ };
1723
+ await walk(root);
1724
+ return out.sort();
1725
+ }
1726
+ function createSandboxBackend(opts = {}) {
1727
+ const root = resolve3(opts.root ?? process.cwd());
1728
+ const timeoutMs = opts.timeoutMs ?? 3e4;
1729
+ const maxOutputChars = opts.maxOutputChars ?? 12e3;
1730
+ const env = baseEnv(opts.env);
1731
+ let spills = 0;
1732
+ const spill = async (text) => {
1733
+ spills += 1;
1734
+ const name = `.spill-${spills}.txt`;
1735
+ await writeFile2(join4(root, name), text, "utf8");
1736
+ return `${text.slice(0, maxOutputChars)}
1737
+ \u2026[truncated ${text.length - maxOutputChars} chars \u2192 read ${name} with read_file]`;
1738
+ };
1739
+ const executeTool = defineTool({
1740
+ name: "execute",
1741
+ description: "Run a shell command in the sandbox workspace (cwd=root, scrubbed env, timeout). Large output spills to a file.",
1742
+ schema: z7.object({ command: z7.string().min(1).max(4e3) }),
1743
+ execute: ({ command }) => new Promise((resolveExec, reject) => {
1744
+ execFile("sh", ["-c", command], { cwd: root, env, timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024 }, (err, stdout, stderr) => {
1745
+ if (err && err.killed) {
1746
+ if (err.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
1747
+ reject(new AgentError("SANDBOX_OUTPUT_TOO_LARGE", "Command output exceeded the 4MB buffer; write output to a file and read it in chunks", false));
1748
+ return;
1749
+ }
1750
+ reject(new AgentError("SANDBOX_TIMEOUT", `Command exceeded ${timeoutMs}ms`, true));
1751
+ return;
1752
+ }
1753
+ const code = err && "code" in err ? err.code : 0;
1754
+ const combined = `${stdout}${stderr ? `
1755
+ [stderr]
1756
+ ${stderr}` : ""}`;
1757
+ if (combined.length <= maxOutputChars) {
1758
+ resolveExec(err ? `${combined}
1759
+ [Command failed with exit code ${code}]` : combined || "[Command succeeded with exit code 0]");
1760
+ return;
1761
+ }
1762
+ void spill(combined).then(
1763
+ (pointer) => resolveExec(`${pointer}
1764
+ [exit ${code}]`),
1765
+ reject
1766
+ );
1767
+ });
1768
+ })
1769
+ });
1770
+ const globTool = defineTool({
1771
+ name: "glob",
1772
+ description: "List workspace files matching a glob (*, ?, **).",
1773
+ schema: z7.object({ pattern: z7.string().min(1).max(300) }),
1774
+ execute: async ({ pattern }) => (await walkFiles(root)).filter((f) => matchGlob(pattern, f)).join("\n")
1775
+ });
1776
+ const grepTool = defineTool({
1777
+ name: "grep",
1778
+ description: "Search file contents (literal substring) under a glob. Returns path:line matches.",
1779
+ schema: z7.object({ query: z7.string().min(1).max(300), pattern: z7.string().default("**") }),
1780
+ execute: async ({ query, pattern }) => {
1781
+ const files = (await walkFiles(root)).filter((f) => matchGlob(pattern ?? "**", f));
1782
+ const hits = [];
1783
+ for (const f of files.slice(0, 200)) {
1784
+ let info;
1785
+ try {
1786
+ info = await stat2(join4(root, f));
1787
+ } catch {
1788
+ continue;
1789
+ }
1790
+ if (info.size > 512 * 1024) continue;
1791
+ let content;
1792
+ try {
1793
+ content = await readFile2(join4(root, f), "utf8");
1794
+ } catch {
1795
+ continue;
1796
+ }
1797
+ content.split("\n").forEach((line, i) => {
1798
+ if (line.includes(query)) hits.push(`${f}:${i + 1}:${line.slice(0, 200)}`);
1799
+ });
1800
+ if (hits.length >= 50) break;
1801
+ }
1802
+ return hits.join("\n") || "(no matches)";
1803
+ }
1804
+ });
1805
+ return {
1806
+ root,
1807
+ tools: () => [...filesystemTools(root), globTool, grepTool, executeTool]
1808
+ };
1809
+ }
1810
+
1811
+ // src/evals/index.ts
1812
+ import { readFileSync as readFileSync3 } from "fs";
1813
+ function loadJsonl(path) {
1814
+ return readFileSync3(path, "utf8").split("\n").map((l) => l.trim()).filter(Boolean).map((l) => JSON.parse(l));
1815
+ }
1816
+ async function runEval(cases, makeAgent) {
1817
+ const scores = [];
1818
+ for (const c of cases) {
1819
+ try {
1820
+ const res = await makeAgent(c).run(c.input);
1821
+ const missing = (c.expect?.contains ?? []).filter((s) => !res.text.includes(s));
1822
+ const missingTools = (c.expect?.toolUsed ?? []).filter((t) => !res.toolCalls.some((tc) => tc.name === t));
1823
+ const pass = !missing.length && !missingTools.length;
1824
+ scores.push({
1825
+ id: c.id,
1826
+ pass,
1827
+ detail: pass ? "ok" : `missing text ${JSON.stringify(missing)} tools ${JSON.stringify(missingTools)} :: ${res.text.slice(0, 120)}`,
1828
+ usage: res.usage
1829
+ });
1830
+ } catch (err) {
1831
+ scores.push({
1832
+ id: c.id,
1833
+ pass: false,
1834
+ detail: `threw: ${err instanceof Error ? err.message : String(err)}`,
1835
+ usage: { inputTokens: 0, outputTokens: 0 }
1836
+ });
1837
+ }
1838
+ }
1839
+ const passRate = scores.length ? scores.filter((s) => s.pass).length / scores.length : 1;
1840
+ return { scores, passRate };
1841
+ }
1842
+ function printReport(scores, passRate) {
1843
+ for (const s of scores) {
1844
+ console.log(`${s.pass ? "PASS" : "FAIL"} ${s.id} ${s.detail}`);
1845
+ }
1846
+ console.log(`
1847
+ pass rate: ${(passRate * 100).toFixed(1)}% (${scores.filter((s) => s.pass).length}/${scores.length})`);
1848
+ }
1849
+
1850
+ // src/tracing/index.ts
1851
+ import { appendFileSync, mkdirSync } from "fs";
1852
+ import { dirname as dirname3 } from "path";
1853
+ function hex(n) {
1854
+ return [...Array(n)].map(() => Math.floor(Math.random() * 16).toString(16)).join("");
1855
+ }
1856
+ var ConsoleSpanSink = class {
1857
+ emit(span) {
1858
+ console.log(`[trace] ${span.name}`, JSON.stringify(span.attributes));
1859
+ }
1860
+ };
1861
+ var JsonlSpanSink = class {
1862
+ constructor(path) {
1863
+ this.path = path;
1864
+ mkdirSync(dirname3(path), { recursive: true });
1865
+ }
1866
+ path;
1867
+ emit(span) {
1868
+ appendFileSync(this.path, `${JSON.stringify(span)}
1869
+ `, "utf8");
1870
+ }
1871
+ };
1872
+ var MemorySpanSink = class {
1873
+ spans = [];
1874
+ emit(span) {
1875
+ this.spans.push(span);
1876
+ }
1877
+ };
1878
+ function tracingEvents(sink, opts = {}) {
1879
+ const traceId = opts.traceId ?? hex(32);
1880
+ const open2 = /* @__PURE__ */ new Map();
1881
+ const emit = (name, attributes, parentId) => {
1882
+ const span = {
1883
+ traceId,
1884
+ spanId: hex(16),
1885
+ ...parentId ? { parentId } : {},
1886
+ name,
1887
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1888
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
1889
+ attributes
1890
+ };
1891
+ sink.emit(span);
1892
+ return span;
1893
+ };
1894
+ return {
1895
+ onStep: (e) => {
1896
+ emit("agent.step", { step: e.step, messages: e.messages.length });
1897
+ },
1898
+ onToolCall: (e) => {
1899
+ const span = emit("tool.call", { tool: e.tool, args: e.args });
1900
+ open2.set(e.tool, span);
1901
+ },
1902
+ onToolResult: (e) => {
1903
+ const parent = open2.get(e.tool);
1904
+ open2.delete(e.tool);
1905
+ emit("tool.result", { tool: e.tool, output: e.output }, parent?.spanId);
1906
+ },
1907
+ onToken: (delta) => {
1908
+ emit("model.token", { delta: delta.slice(0, 200) });
1909
+ },
1910
+ onThinking: (delta) => {
1911
+ emit("model.thinking", { delta: delta.slice(0, 200) });
1912
+ },
1913
+ onDone: (e) => {
1914
+ emit("agent.done", { text: e.text.slice(0, 500), toolCalls: e.toolCalls.length });
1915
+ }
1916
+ };
1917
+ }
1918
+ function policyFilterMiddleware(opts) {
1919
+ const match = (list, name) => (
1920
+ // `g`-flagged regexes are stateful across .test() calls — clone without
1921
+ // the flag so one tool name can't alternate denied/allowed.
1922
+ (list ?? []).some(
1923
+ (p) => typeof p === "string" ? p === name : new RegExp(p.source, p.flags.replace("g", "")).test(name)
1924
+ )
1925
+ );
1926
+ return {
1927
+ name: "policy-filter",
1928
+ beforeTool: (call) => {
1929
+ if (opts.deny && match(opts.deny, call.name)) {
1930
+ if (opts.exitBehavior === "error") {
1931
+ throw new AgentError("POLICY_DENY", `Policy denies tool "${call.name}"`, false);
1932
+ }
1933
+ throw new ToolRejection(`Policy denies tool "${call.name}". Do not retry it; finish another way.`);
1934
+ }
1935
+ if (opts.allow && !match(opts.allow, call.name)) {
1936
+ throw new ToolRejection(`Policy allows only [${opts.allow.join(", ")}]; "${call.name}" is not allowed.`);
1937
+ }
1938
+ return void 0;
1939
+ }
1940
+ };
1941
+ }
1942
+
1943
+ // src/streaming/index.ts
1944
+ async function collectStream(stream) {
1945
+ const events = [];
1946
+ for (; ; ) {
1947
+ const next = await stream.next();
1948
+ if (next.done) return { events, result: next.value };
1949
+ events.push(next.value);
1950
+ }
1951
+ }
1952
+
1953
+ // src/memory/compaction.ts
1954
+ function defaultSummarize(messages) {
1955
+ const head = messages.filter((m) => m.role !== "system").slice(0, 5).map((m) => `[${m.role}] ${textOf(m.content).slice(0, 200)}`).join("\n");
1956
+ return Promise.resolve(`Prior context summary (${messages.length} msgs):
1957
+ ${head}`);
1958
+ }
1959
+ var CompactedMemory = class {
1960
+ constructor(inner, opts = {}) {
1961
+ this.inner = inner;
1962
+ this.opts = opts;
1963
+ }
1964
+ inner;
1965
+ opts;
1966
+ async load() {
1967
+ return this.inner.load();
1968
+ }
1969
+ async save(messages) {
1970
+ await this.inner.save(messages);
1971
+ const max = this.opts.maxMessages ?? 40;
1972
+ const current = await this.inner.load();
1973
+ if (current.length <= max) return;
1974
+ const summarize = this.opts.summarize ?? defaultSummarize;
1975
+ const overflow = current.slice(0, current.length - max + 1);
1976
+ const rest = current.slice(current.length - max + 1);
1977
+ const summary = await summarize(overflow);
1978
+ await this.inner.clear();
1979
+ await this.inner.save([{ role: "assistant", content: summary }, ...rest]);
1980
+ }
1981
+ async clear() {
1982
+ return this.inner.clear();
1983
+ }
1984
+ };
1985
+
1986
+ // src/models/index.ts
1987
+ var MockModelProvider = class {
1988
+ id = "mock";
1989
+ capabilities = { structuredOutput: false, streaming: false };
1990
+ calls = 0;
1991
+ async complete(_options) {
1992
+ this.calls += 1;
1993
+ return {
1994
+ text: `mock-response-${this.calls}`,
1995
+ toolCalls: [],
1996
+ usage: { inputTokens: 10, outputTokens: 5 }
1997
+ };
1998
+ }
1999
+ };
2000
+
2001
+ // src/observability/index.ts
2002
+ function supportsColor() {
2003
+ return !process.env.NO_COLOR && !!process.stdout?.isTTY;
2004
+ }
2005
+ var C = {
2006
+ reset: "\x1B[0m",
2007
+ bold: "\x1B[1m",
2008
+ dim: "\x1B[2m",
2009
+ cyan: "\x1B[36m",
2010
+ yellow: "\x1B[33m",
2011
+ green: "\x1B[32m",
2012
+ magenta: "\x1B[35m",
2013
+ red: "\x1B[31m"
2014
+ };
2015
+ function paint(color, text, on) {
2016
+ if (!on || color === "reset") return text;
2017
+ return `${C[color]}${text}${C.reset}`;
2018
+ }
2019
+ function consoleEvents(prefixOrOpts = "[agent]", maybeOpts = {}) {
2020
+ const { prefix, colors } = typeof prefixOrOpts === "string" ? { prefix: prefixOrOpts, colors: maybeOpts.colors ?? supportsColor() } : { prefix: prefixOrOpts.prefix ?? "[agent]", colors: prefixOrOpts.colors ?? supportsColor() };
2021
+ const tag = (color, label) => `${paint(color, `${prefix} ${label}`, colors)}`;
2022
+ return {
2023
+ onStep: (e) => console.log(tag("cyan", `step ${e.step} (${e.messages.length} msgs)`)),
2024
+ onToolCall: (e) => console.log(tag("yellow", `tool:call ${e.tool}`), e.args),
2025
+ onToolResult: (e) => console.log(tag("green", `tool:result ${e.tool}`), e.output),
2026
+ onDone: (e) => console.log(tag("green", paint("bold", "done:", colors)), e.text),
2027
+ onToken: (d) => process.stdout.write(d),
2028
+ onThinking: (d) => process.stdout.write(paint("dim", d, colors))
2029
+ };
2030
+ }
2031
+ function logError(prefix, err, colors) {
2032
+ const on = colors ?? supportsColor();
2033
+ const message = err instanceof Error ? err.stack ?? err.message : String(err);
2034
+ console.error(paint("red", `${prefix} error:`, on), message);
2035
+ }
2036
+
2037
+ // src/orchestration/index.ts
2038
+ function requireAgents(plan) {
2039
+ if (!plan.agents.length) throw new AgentError("ORCHESTRATION_EMPTY", "Orchestration needs at least one agent", false);
2040
+ }
2041
+ async function runSequential(plan, input, opts = {}) {
2042
+ requireAgents(plan);
2043
+ const results = [];
2044
+ let current = input;
2045
+ for (let i = 0; i < plan.agents.length; i++) {
2046
+ opts.signal?.throwIfAborted();
2047
+ plan.events?.onStep?.({ step: i, messages: [] });
2048
+ const res = await plan.agents[i].run(current, { signal: opts.signal, invocationState: opts.invocationState });
2049
+ results.push(res);
2050
+ current = `${input}
2051
+
2052
+ Previous result (${plan.agents[i].name}):
2053
+ ${res.text}`;
2054
+ }
2055
+ return { strategy: "sequential", results, text: results.at(-1)?.text ?? "" };
2056
+ }
2057
+ async function runParallel(plan, input, opts = {}) {
2058
+ requireAgents(plan);
2059
+ const limit = Math.max(1, plan.concurrency ?? plan.agents.length);
2060
+ const results = new Array(plan.agents.length);
2061
+ let next = 0;
2062
+ const workers = Array.from({ length: Math.min(limit, plan.agents.length) }, async () => {
2063
+ for (; ; ) {
2064
+ opts.signal?.throwIfAborted();
2065
+ const i = next++;
2066
+ if (i >= plan.agents.length) return;
2067
+ results[i] = await plan.agents[i].run(input, { signal: opts.signal, invocationState: opts.invocationState });
2068
+ }
2069
+ });
2070
+ await Promise.all(workers);
2071
+ return { strategy: "parallel", results, text: results.map((r) => r.text).join("\n\n") };
2072
+ }
2073
+ async function runRouter(plan, input, opts = {}) {
2074
+ requireAgents(plan);
2075
+ const routerModel = plan.routerModel ?? plan.agents[0].model;
2076
+ const menu = plan.agents.map((a, i) => `${i}: ${a.name}${a.description ? ` \u2014 ${a.description}` : ""}`).join("\n");
2077
+ const pick = await routerModel.complete({
2078
+ messages: [
2079
+ { role: "system", content: "You route user requests to specialist agents. Reply with ONLY the agent number." },
2080
+ { role: "user", content: `Agents:
2081
+ ${menu}
2082
+
2083
+ Request: ${input}` }
2084
+ ],
2085
+ tools: []
2086
+ });
2087
+ const match = pick.text.match(/\d+/);
2088
+ const idx = match ? Number.parseInt(match[0], 10) : Number.NaN;
2089
+ if (!Number.isInteger(idx) || idx < 0 || idx >= plan.agents.length) {
2090
+ throw new AgentError("ROUTER_NO_MATCH", `Router picked invalid agent: ${JSON.stringify(pick.text)}`, false);
2091
+ }
2092
+ const res = await plan.agents[idx].run(input, { signal: opts.signal, invocationState: opts.invocationState });
2093
+ return { strategy: "router", results: [res], text: res.text, routedTo: idx };
2094
+ }
2095
+ async function runOrchestrated(plan, input, signalOrOpts) {
2096
+ const opts = signalOrOpts instanceof AbortSignal ? { signal: signalOrOpts } : signalOrOpts ?? {};
2097
+ switch (plan.strategy) {
2098
+ case "sequential":
2099
+ return runSequential(plan, input, opts);
2100
+ case "parallel":
2101
+ return runParallel(plan, input, opts);
2102
+ case "router":
2103
+ return runRouter(plan, input, opts);
2104
+ }
2105
+ }
2106
+
2107
+ // src/workflows/index.ts
2108
+ function sortTopo(nodes) {
2109
+ const byId = new Map(nodes.map((n) => [n.id, n]));
2110
+ if (byId.size !== nodes.length) throw new AgentError("WORKFLOW_DUPLICATE_ID", "Workflow node ids must be unique", false);
2111
+ for (const n of nodes) {
2112
+ for (const dep of n.dependsOn ?? []) {
2113
+ if (!byId.has(dep)) throw new AgentError("WORKFLOW_UNKNOWN_DEP", `Node "${n.id}" depends on unknown node "${dep}"`, false);
2114
+ if (dep === n.id) throw new AgentError("WORKFLOW_CYCLE", `Node "${n.id}" depends on itself`, false);
2115
+ }
2116
+ }
2117
+ const indegree = new Map(nodes.map((n) => [n.id, n.dependsOn ? new Set(n.dependsOn).size : 0]));
2118
+ const levels = [];
2119
+ let ready = nodes.filter((n) => (indegree.get(n.id) ?? 0) === 0);
2120
+ while (ready.length) {
2121
+ levels.push(ready);
2122
+ const next = [];
2123
+ for (const n of nodes) {
2124
+ if (!ready.includes(n)) continue;
2125
+ for (const m of nodes) {
2126
+ if (!(m.dependsOn ?? []).includes(n.id)) continue;
2127
+ indegree.set(m.id, (indegree.get(m.id) ?? 1) - 1);
2128
+ if (indegree.get(m.id) === 0) next.push(m);
2129
+ }
2130
+ }
2131
+ ready = next;
2132
+ }
2133
+ if (levels.flat().length !== nodes.length) {
2134
+ throw new AgentError("WORKFLOW_CYCLE", "Workflow has a dependency cycle", false);
2135
+ }
2136
+ return levels;
2137
+ }
2138
+ async function runWithRetry(node, ctx, signal, invocationState) {
2139
+ const attempts = Math.max(1, node.retry?.attempts ?? 1);
2140
+ const backoffMs = node.retry?.backoffMs ?? 0;
2141
+ let last;
2142
+ for (let i = 0; i < attempts; i++) {
2143
+ signal?.throwIfAborted();
2144
+ try {
2145
+ return await node.run(ctx, invocationState);
2146
+ } catch (err) {
2147
+ if (err instanceof DOMException && err.name === "AbortError") throw err;
2148
+ last = err;
2149
+ if (i < attempts - 1 && backoffMs > 0) await sleep(backoffMs, signal);
2150
+ }
2151
+ }
2152
+ throw last;
2153
+ }
2154
+ async function runWorkflow(nodes, opts = {}) {
2155
+ const ctx = {};
2156
+ for (const level of sortTopo(nodes)) {
2157
+ opts.signal?.throwIfAborted();
2158
+ const entries = await Promise.all(
2159
+ level.map(async (n) => [n.id, await runWithRetry(n, ctx, opts.signal, opts.invocationState)])
2160
+ );
2161
+ for (const [id, value] of entries) ctx[id] = value;
2162
+ }
2163
+ return ctx;
2164
+ }
2165
+
2166
+ // src/mcp/index.ts
2167
+ import { spawn } from "child_process";
2168
+ import { z as z8 } from "zod";
2169
+ var rpcSeq = 0;
2170
+ function jsonSchemaToZod(schema) {
2171
+ const type = schema.type;
2172
+ if (Array.isArray(schema.enum)) {
2173
+ const values = schema.enum;
2174
+ if (values.length && values.every((v) => typeof v === "string")) return z8.enum(values);
2175
+ if (values.length) {
2176
+ const literals = values.map((v) => z8.literal(v));
2177
+ return literals.length > 1 ? z8.union(literals) : literals[0];
2178
+ }
2179
+ return z8.never();
2180
+ }
2181
+ switch (type) {
2182
+ case "string":
2183
+ return z8.string();
2184
+ case "number":
2185
+ return z8.number();
2186
+ case "integer":
2187
+ return z8.number().int();
2188
+ case "boolean":
2189
+ return z8.boolean();
2190
+ case "null":
2191
+ return z8.null();
2192
+ case "array": {
2193
+ const items = schema.items;
2194
+ return z8.array(items ? jsonSchemaToZod(items) : z8.unknown());
2195
+ }
2196
+ case "object": {
2197
+ const props = schema.properties ?? {};
2198
+ const required = new Set(schema.required ?? []);
2199
+ const shape = {};
2200
+ for (const [key, sub] of Object.entries(props)) {
2201
+ const inner = jsonSchemaToZod(sub);
2202
+ shape[key] = required.has(key) ? inner : inner.optional();
2203
+ }
2204
+ return z8.object(shape).passthrough();
2205
+ }
2206
+ default:
2207
+ return z8.unknown();
2208
+ }
2209
+ }
2210
+ async function parseStreamableResponse(res) {
2211
+ const contentType = res.headers.get("content-type") ?? "";
2212
+ if (contentType.includes("text/event-stream")) {
2213
+ const text = await res.text();
2214
+ let last;
2215
+ for (const line of text.split("\n")) {
2216
+ const t = line.trim();
2217
+ if (!t.startsWith("data:")) continue;
2218
+ const payload = t.slice(5).trim();
2219
+ if (payload === "[DONE]") continue;
2220
+ try {
2221
+ const msg = JSON.parse(payload);
2222
+ if (msg.id !== void 0 || msg.result !== void 0 || msg.error) last = msg;
2223
+ } catch {
2224
+ continue;
2225
+ }
2226
+ }
2227
+ if (!last) throw new AgentError("MCP_PROTOCOL", "Empty SSE response from MCP server", true);
2228
+ return last;
2229
+ }
2230
+ return await res.json();
2231
+ }
2232
+ function checkRpc(res, what) {
2233
+ if (res.error) throw new AgentError("MCP_ERROR", `MCP ${what}: ${res.error.message} (${res.error.code})`, false);
2234
+ return res.result;
2235
+ }
2236
+ var McpHttpClient = class {
2237
+ serverId;
2238
+ url;
2239
+ headers;
2240
+ fetchFn;
2241
+ clientName;
2242
+ sessionId;
2243
+ connected = false;
2244
+ constructor(url, opts = {}) {
2245
+ this.url = url.replace(/\/$/, "");
2246
+ this.serverId = opts.serverId ?? url;
2247
+ this.headers = opts.headers ?? {};
2248
+ this.fetchFn = opts.fetchFn ?? fetch;
2249
+ this.clientName = opts.clientName ?? "fabisch-agents";
2250
+ }
2251
+ async rpc(method, params) {
2252
+ return this.rpcInner(method, params, true);
2253
+ }
2254
+ /** JSON-RPC notification — no id, no response expected. */
2255
+ async notify(method, params) {
2256
+ await this.rpcInner(method, params, false);
2257
+ }
2258
+ async rpcInner(method, params, withId) {
2259
+ let res;
2260
+ try {
2261
+ res = await this.fetchFn(this.url, {
2262
+ method: "POST",
2263
+ headers: {
2264
+ "content-type": "application/json",
2265
+ accept: "application/json, text/event-stream",
2266
+ ...this.sessionId ? { "mcp-session-id": this.sessionId } : {},
2267
+ ...this.headers
2268
+ },
2269
+ body: JSON.stringify(
2270
+ withId ? { jsonrpc: "2.0", id: ++rpcSeq, method, params: params ?? {} } : { jsonrpc: "2.0", method, params: params ?? {} }
2271
+ )
2272
+ });
2273
+ } catch (err) {
2274
+ throw new AgentError("MCP_TRANSPORT", `MCP request failed: ${err instanceof Error ? err.message : String(err)}`, true);
2275
+ }
2276
+ const session = res.headers.get("mcp-session-id");
2277
+ if (session) this.sessionId = session;
2278
+ if (!res.ok) throw new AgentError("MCP_TRANSPORT", `MCP HTTP ${res.status}`, res.status >= 500);
2279
+ if (!withId) {
2280
+ await res.text().catch(() => "");
2281
+ return void 0;
2282
+ }
2283
+ return checkRpc(await parseStreamableResponse(res), method);
2284
+ }
2285
+ async connect() {
2286
+ const result = await this.rpc("initialize", {
2287
+ protocolVersion: "2024-11-05",
2288
+ capabilities: {},
2289
+ clientInfo: { name: this.clientName, version: "0.1.0" }
2290
+ });
2291
+ await this.notify("notifications/initialized", {}).catch(() => void 0);
2292
+ this.connected = true;
2293
+ return { name: result.serverInfo?.name ?? this.serverId, version: result.serverInfo?.version };
2294
+ }
2295
+ requireConnected() {
2296
+ if (!this.connected) throw new AgentError("MCP_NOT_CONNECTED", "Call connect() first", false);
2297
+ }
2298
+ async listTools() {
2299
+ this.requireConnected();
2300
+ const result = await this.rpc("tools/list");
2301
+ return result.tools ?? [];
2302
+ }
2303
+ async callTool(name, args) {
2304
+ this.requireConnected();
2305
+ const result = await this.rpc("tools/call", { name, arguments: args ?? {} });
2306
+ const text = (result.content ?? []).filter((c) => c.type === "text").map((c) => c.text ?? "").join("\n");
2307
+ if (result.isError) throw new AgentError("MCP_TOOL_ERROR", `MCP tool "${name}" failed: ${text}`, false);
2308
+ return text;
2309
+ }
2310
+ async close() {
2311
+ this.connected = false;
2312
+ this.sessionId = void 0;
2313
+ }
2314
+ };
2315
+ var McpStdioClient = class {
2316
+ constructor(command, args = [], opts = {}) {
2317
+ this.command = command;
2318
+ this.args = args;
2319
+ this.serverId = opts.serverId ?? `${command} ${args.join(" ")}`.trim();
2320
+ this.clientName = opts.clientName ?? "fabisch-agents";
2321
+ }
2322
+ command;
2323
+ args;
2324
+ serverId;
2325
+ child;
2326
+ buffer = "";
2327
+ pending = /* @__PURE__ */ new Map();
2328
+ clientName;
2329
+ async connect() {
2330
+ try {
2331
+ this.child = spawn(this.command, this.args, { stdio: ["pipe", "pipe", "ignore"] });
2332
+ } catch (err) {
2333
+ throw new AgentError("MCP_TRANSPORT", `Cannot spawn MCP server: ${err instanceof Error ? err.message : String(err)}`, false);
2334
+ }
2335
+ if (!this.child.stdout || !this.child.stdin) {
2336
+ throw new AgentError("MCP_TRANSPORT", "MCP stdio pipes unavailable", false);
2337
+ }
2338
+ this.child.on("error", (err) => {
2339
+ for (const [, respond] of this.pending) {
2340
+ respond({ jsonrpc: "2.0", error: { code: -32e3, message: `MCP server failed: ${err.message}` } });
2341
+ }
2342
+ this.pending.clear();
2343
+ });
2344
+ this.child.on("exit", (code, signal) => {
2345
+ for (const [, respond] of this.pending) {
2346
+ respond({ jsonrpc: "2.0", error: { code: -32e3, message: `MCP server exited (code ${code ?? "null"}, signal ${signal ?? "none"})` } });
2347
+ }
2348
+ this.pending.clear();
2349
+ });
2350
+ this.child.stdout.on("data", (chunk) => {
2351
+ this.buffer += chunk.toString("utf8");
2352
+ const lines = this.buffer.split("\n");
2353
+ this.buffer = lines.pop() ?? "";
2354
+ for (const line of lines) {
2355
+ const t = line.trim();
2356
+ if (!t) continue;
2357
+ try {
2358
+ const msg = JSON.parse(t);
2359
+ if (msg.id !== void 0) this.pending.get(msg.id)?.(msg);
2360
+ } catch {
2361
+ continue;
2362
+ }
2363
+ }
2364
+ });
2365
+ const result = await this.request("initialize", {
2366
+ protocolVersion: "2024-11-05",
2367
+ capabilities: {},
2368
+ clientInfo: { name: this.clientName, version: "0.1.0" }
2369
+ });
2370
+ void result;
2371
+ }
2372
+ request(method, params) {
2373
+ return new Promise((resolve4, reject) => {
2374
+ if (!this.child?.stdin) {
2375
+ reject(new AgentError("MCP_NOT_CONNECTED", "Call connect() first", false));
2376
+ return;
2377
+ }
2378
+ const id = ++rpcSeq;
2379
+ this.pending.set(id, (res) => {
2380
+ this.pending.delete(id);
2381
+ try {
2382
+ resolve4(checkRpc(res, method));
2383
+ } catch (err) {
2384
+ reject(err);
2385
+ }
2386
+ });
2387
+ this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
2388
+ `, (err) => {
2389
+ if (err) {
2390
+ this.pending.delete(id);
2391
+ reject(new AgentError("MCP_TRANSPORT", `MCP stdio write failed: ${err.message}`, true));
2392
+ }
2393
+ });
2394
+ });
2395
+ }
2396
+ async listTools() {
2397
+ const result = await this.request("tools/list", {});
2398
+ return result.tools ?? [];
2399
+ }
2400
+ async callTool(name, args) {
2401
+ const result = await this.request("tools/call", { name, arguments: args ?? {} });
2402
+ const text = (result.content ?? []).filter((c) => c.type === "text").map((c) => c.text ?? "").join("\n");
2403
+ if (result.isError) throw new AgentError("MCP_TOOL_ERROR", `MCP tool "${name}" failed: ${text}`, false);
2404
+ return text;
2405
+ }
2406
+ async close() {
2407
+ this.child?.kill();
2408
+ this.child = void 0;
2409
+ for (const [, respond] of this.pending) {
2410
+ respond({ jsonrpc: "2.0", error: { code: -32e3, message: "MCP client closed" } });
2411
+ }
2412
+ this.pending.clear();
2413
+ }
2414
+ };
2415
+ async function mcpToolsToFramework(client, defs) {
2416
+ const list = defs ?? await client.listTools();
2417
+ const seen = /* @__PURE__ */ new Set();
2418
+ return list.map((d) => {
2419
+ const name = seen.has(d.name) ? `${client.serverId}.${d.name}` : d.name;
2420
+ seen.add(d.name);
2421
+ return defineTool({
2422
+ name,
2423
+ description: d.description ?? `MCP tool ${d.name} (via ${client.serverId})`,
2424
+ schema: jsonSchemaToZod(d.inputSchema ?? { type: "object" }),
2425
+ execute: async (args) => client.callTool(d.name, args)
2426
+ });
2427
+ });
2428
+ }
2429
+ async function connectMcpHttp(url, opts) {
2430
+ const client = new McpHttpClient(url, opts);
2431
+ await client.connect();
2432
+ return client;
2433
+ }
2434
+ async function connectMcpStdio(command, args = []) {
2435
+ const client = new McpStdioClient(command, args);
2436
+ await client.connect();
2437
+ return client;
2438
+ }
2439
+
2440
+ // src/prompts/index.ts
2441
+ import { readFileSync as readFileSync4 } from "fs";
2442
+ function definePrompt(name, template, version = "0.1.0") {
2443
+ return {
2444
+ name,
2445
+ version,
2446
+ template,
2447
+ render(vars) {
2448
+ return template.replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => {
2449
+ if (!(key in vars)) throw new Error(`Missing prompt var: ${key} (prompt: ${name})`);
2450
+ return String(vars[key]);
2451
+ });
2452
+ }
2453
+ };
2454
+ }
2455
+ function loadPromptFile(path) {
2456
+ const file = readFileSync4(path, "utf8");
2457
+ const raw = file.startsWith("\uFEFF") ? file.slice(1) : file;
2458
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
2459
+ if (!match) throw new Error(`Invalid prompt file (missing frontmatter): ${path}`);
2460
+ const meta = {};
2461
+ for (const line of match[1].split("\n")) {
2462
+ const i = line.indexOf(":");
2463
+ if (i > 0) meta[line.slice(0, i).trim()] = line.slice(i + 1).trim().replace(/^["']|["']$/g, "");
2464
+ }
2465
+ if (!meta.name) throw new Error(`Invalid prompt file (missing name): ${path}`);
2466
+ return definePrompt(meta.name, match[2].trim(), meta.version ?? "0.1.0");
2467
+ }
2468
+ function abPrompts(variants, pick) {
2469
+ if (pick) {
2470
+ const found = variants.find((v) => (v.version ? `${v.name}@${v.version}` : v.name) === pick || v.name === pick);
2471
+ if (!found) throw new Error(`Unknown prompt variant: ${pick}`);
2472
+ return definePrompt(found.name, found.template, found.version);
2473
+ }
2474
+ const total = variants.reduce((n, v) => n + (v.weight ?? 1), 0);
2475
+ let roll = Math.random() * total;
2476
+ for (const v of variants) {
2477
+ roll -= v.weight ?? 1;
2478
+ if (roll <= 0) return definePrompt(v.name, v.template, v.version);
2479
+ }
2480
+ const last = variants[variants.length - 1];
2481
+ return definePrompt(last.name, last.template, last.version);
2482
+ }
2483
+
2484
+ // src/semantic/index.ts
2485
+ import { z as z12 } from "zod";
2486
+
2487
+ // src/semantic/remote-sparql.ts
2488
+ import { z as z9 } from "zod";
2489
+ var DEFAULT_PREFIXES = {
2490
+ rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
2491
+ rdfs: "http://www.w3.org/2000/01/rdf-schema#",
2492
+ owl: "http://www.w3.org/2002/07/owl#",
2493
+ xsd: "http://www.w3.org/2001/XMLSchema#",
2494
+ skos: "http://www.w3.org/2004/02/skos/core#",
2495
+ dct: "http://purl.org/dc/terms/",
2496
+ prov: "http://www.w3.org/ns/prov#",
2497
+ schema: "https://schema.org/"
2498
+ };
2499
+ var RemoteSparqlAdapter = class {
2500
+ endpoint;
2501
+ updateEndpoint;
2502
+ prefixes;
2503
+ extraHeaders;
2504
+ fetchFn;
2505
+ timeoutMs;
2506
+ maxRetries;
2507
+ auth;
2508
+ constructor(opts) {
2509
+ this.endpoint = opts.endpoint.replace(/\/$/, "");
2510
+ this.updateEndpoint = (opts.updateEndpoint ?? opts.endpoint).replace(/\/$/, "");
2511
+ this.prefixes = { ...DEFAULT_PREFIXES, ...opts.prefixes };
2512
+ this.extraHeaders = opts.headers ?? {};
2513
+ this.fetchFn = opts.fetchFn ?? fetch;
2514
+ this.timeoutMs = opts.timeoutMs ?? 3e4;
2515
+ this.maxRetries = Math.max(0, opts.maxRetries ?? 2);
2516
+ this.auth = opts.auth;
2517
+ }
2518
+ /** Prepend pinned PREFIX declarations so caller queries stay short. */
2519
+ withPrefixes(query) {
2520
+ const declarations = Object.entries(this.prefixes).map(([prefix, iri]) => `PREFIX ${prefix}: <${iri}>`).join("\n");
2521
+ return `${declarations}
2522
+ ${query}`;
2523
+ }
2524
+ headers(extra = {}) {
2525
+ const out = { ...extra, ...this.extraHeaders };
2526
+ out["user-agent"] ??= "agent-framework/0.1 (+RemoteSparqlAdapter)";
2527
+ if (!this.auth) return out;
2528
+ if (this.auth.type === "basic") {
2529
+ out.authorization = `Basic ${Buffer.from(`${this.auth.user}:${this.auth.password}`).toString("base64")}`;
2530
+ } else if (this.auth.type === "bearer") {
2531
+ out.authorization = `Bearer ${this.auth.token}`;
2532
+ } else {
2533
+ out[this.auth.name] = this.auth.value;
2534
+ }
2535
+ return out;
2536
+ }
2537
+ async post(url, contentType, body, accept) {
2538
+ let lastError;
2539
+ let retryDelayMs;
2540
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
2541
+ if (attempt > 0) {
2542
+ await new Promise((r) => setTimeout(r, retryDelayMs ?? Math.min(2e3 * 2 ** (attempt - 1), 1e4)));
2543
+ }
2544
+ let res;
2545
+ try {
2546
+ res = await this.fetchFn(url, {
2547
+ method: "POST",
2548
+ headers: this.headers({ "content-type": contentType, accept }),
2549
+ body,
2550
+ signal: AbortSignal.timeout(this.timeoutMs)
2551
+ });
2552
+ } catch (err) {
2553
+ lastError = new AgentError(
2554
+ "REMOTE_SPARQL",
2555
+ `SPARQL request failed: ${err instanceof Error ? err.message : String(err)}`,
2556
+ true
2557
+ );
2558
+ retryDelayMs = void 0;
2559
+ continue;
2560
+ }
2561
+ if (!res.ok) {
2562
+ const text = await res.text().catch(() => "");
2563
+ const retryable = res.status === 429 || res.status >= 500;
2564
+ const err = new AgentError(
2565
+ "REMOTE_SPARQL",
2566
+ `${url} HTTP ${res.status}: ${text.slice(0, 300)}`,
2567
+ retryable
2568
+ );
2569
+ const retryAfterHeader = Number.parseInt(res.headers.get("retry-after") ?? "", 10);
2570
+ retryDelayMs = Number.isFinite(retryAfterHeader) ? Math.min(retryAfterHeader * 1e3, 6e4) : void 0;
2571
+ if (!retryable || attempt === this.maxRetries) throw err;
2572
+ lastError = err;
2573
+ continue;
2574
+ }
2575
+ return res;
2576
+ }
2577
+ throw lastError ?? new AgentError("REMOTE_SPARQL", "request failed", true);
2578
+ }
2579
+ /**
2580
+ * SELECT over the remote endpoint. Returns rows of plain-string terms
2581
+ * (same shape as the local `executeSelect`) so both path kinds compose.
2582
+ */
2583
+ async select(query) {
2584
+ const res = await this.post(
2585
+ this.endpoint,
2586
+ "application/sparql-query",
2587
+ this.withPrefixes(query),
2588
+ "application/sparql-results+json"
2589
+ );
2590
+ let json;
2591
+ try {
2592
+ json = await res.json();
2593
+ } catch (err) {
2594
+ throw new AgentError(
2595
+ "REMOTE_SPARQL",
2596
+ `Endpoint returned non-JSON results (expected sparql-results+json): ${err instanceof Error ? err.message : String(err)}`,
2597
+ false
2598
+ );
2599
+ }
2600
+ const vars = json.head?.vars ?? [];
2601
+ const bindings = json.results?.bindings ?? [];
2602
+ return bindings.map((b) => {
2603
+ const row = {};
2604
+ for (const v of vars) row[v] = b[v]?.value ?? "";
2605
+ return row;
2606
+ });
2607
+ }
2608
+ /**
2609
+ * SPARQL 1.1 UPDATE (INSERT/DELETE DATA, DELETE WHERE...) against the
2610
+ * update endpoint. Opt-in — never wire this into a read-only agent.
2611
+ */
2612
+ async update(query) {
2613
+ await this.post(
2614
+ this.updateEndpoint,
2615
+ "application/sparql-update",
2616
+ this.withPrefixes(query),
2617
+ "*/*"
2618
+ );
2619
+ }
2620
+ };
2621
+ function remoteSparqlTool(adapter, opts = {}) {
2622
+ if (!opts.allowUpdate) {
2623
+ return defineTool({
2624
+ name: "remote_sparql_select",
2625
+ description: "Query a remote SPARQL endpoint (read-only SELECT).",
2626
+ schema: z9.object({ query: z9.string().min(1).max(2e4) }),
2627
+ execute: async ({ query }) => adapter.select(query)
2628
+ });
2629
+ }
2630
+ return defineTool({
2631
+ name: "remote_sparql",
2632
+ description: "Query or mutate a remote SPARQL endpoint (SELECT, or UPDATE with { update: true }).",
2633
+ schema: z9.object({ query: z9.string().min(1).max(2e4), update: z9.boolean().default(false) }),
2634
+ execute: async ({ query, update }) => {
2635
+ if (update) {
2636
+ await adapter.update(query);
2637
+ return { updated: true };
2638
+ }
2639
+ return adapter.select(query);
2640
+ }
2641
+ });
2642
+ }
2643
+
2644
+ // src/semantic/entity-linking.ts
2645
+ import { z as z10 } from "zod";
2646
+ var LABEL_PREDICATES = /* @__PURE__ */ new Set([
2647
+ "rdfs:label",
2648
+ "http://www.w3.org/2000/01/rdf-schema#label",
2649
+ "skos:prefLabel",
2650
+ "http://www.w3.org/2004/02/skos/core#prefLabel",
2651
+ "skos:altLabel",
2652
+ "http://www.w3.org/2004/02/skos/core#altLabel"
2653
+ ]);
2654
+ var SAME_AS = /* @__PURE__ */ new Set(["owl:sameAs", "http://www.w3.org/2002/07/owl#sameAs"]);
2655
+ var JSONLD_PREFIXES = {
2656
+ rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
2657
+ rdfs: "http://www.w3.org/2000/01/rdf-schema#",
2658
+ owl: "http://www.w3.org/2002/07/owl#",
2659
+ xsd: "http://www.w3.org/2001/XMLSchema#",
2660
+ skos: "http://www.w3.org/2004/02/skos/core#",
2661
+ dct: "http://purl.org/dc/terms/",
2662
+ prov: "http://www.w3.org/ns/prov#",
2663
+ schema: "https://schema.org/",
2664
+ ex: "urn:example:"
2665
+ };
2666
+ var EntityLinker = class {
2667
+ constructor(store) {
2668
+ this.store = store;
2669
+ }
2670
+ store;
2671
+ /** Union-find over owl:sameAs pairs: every alias cluster -> one representative. */
2672
+ async sameAsGroups() {
2673
+ const pairs = (await this.store.match(null, null, null)).filter((q) => SAME_AS.has(q.predicate));
2674
+ const parent = /* @__PURE__ */ new Map();
2675
+ const find = (x) => {
2676
+ let root = x;
2677
+ while (parent.get(root) !== root && parent.has(root)) root = parent.get(root);
2678
+ return root;
2679
+ };
2680
+ const union = (a, b) => {
2681
+ const ra = find(a);
2682
+ const rb = find(b);
2683
+ if (ra !== rb) parent.set(ra, rb);
2684
+ };
2685
+ for (const q of pairs) {
2686
+ if (!parent.has(q.subject)) parent.set(q.subject, q.subject);
2687
+ if (!parent.has(q.object)) parent.set(q.object, q.object);
2688
+ union(q.subject, q.object);
2689
+ }
2690
+ const representative = /* @__PURE__ */ new Map();
2691
+ for (const member of parent.keys()) representative.set(member, find(member));
2692
+ return representative;
2693
+ }
2694
+ /** Mention -> candidate entities, best first, canonicalized via sameAs. */
2695
+ async link(mention, k = 5) {
2696
+ const needle = mention.trim().toLowerCase();
2697
+ if (!needle) return [];
2698
+ const quads = await this.store.match(null, null, null);
2699
+ const canonical = await this.sameAsGroups();
2700
+ const labels = /* @__PURE__ */ new Map();
2701
+ const labelScore = /* @__PURE__ */ new Map();
2702
+ const candidates = [];
2703
+ for (const q of quads) {
2704
+ const localPart = localName(q.subject);
2705
+ const fullTail = q.subject.includes("/") || q.subject.includes("#") ? q.subject.split(/[#/]/).pop() ?? "" : localPart;
2706
+ if (localPart === needle) candidates.push({ iri: q.subject, score: 40 });
2707
+ else if (localPart.startsWith(needle)) candidates.push({ iri: q.subject, score: 25 });
2708
+ else if (fullTail.toLowerCase() === needle) candidates.push({ iri: q.subject, score: 40 });
2709
+ if (LABEL_PREDICATES.has(q.predicate)) {
2710
+ const label = stripLiteral(q.object).toLowerCase();
2711
+ const trust = q.predicate.includes("altLabel") ? 2 : q.predicate.includes("prefLabel") ? 0 : 1;
2712
+ if (label === needle) candidates.push({ iri: q.subject, label: stripLiteral(q.object), score: 100 });
2713
+ else if (label.startsWith(needle)) candidates.push({ iri: q.subject, label: stripLiteral(q.object), score: 60 });
2714
+ else if (label.includes(needle)) candidates.push({ iri: q.subject, label: stripLiteral(q.object), score: 30 });
2715
+ const prev = labelScore.get(q.subject);
2716
+ if (prev === void 0 || trust < prev) {
2717
+ labels.set(q.subject, stripLiteral(q.object));
2718
+ labelScore.set(q.subject, trust);
2719
+ }
2720
+ }
2721
+ }
2722
+ const merged = /* @__PURE__ */ new Map();
2723
+ for (const c of candidates) {
2724
+ const rep = canonical.get(c.iri) ?? c.iri;
2725
+ const existing = merged.get(rep);
2726
+ if (existing) {
2727
+ existing.score = Math.max(existing.score, c.score);
2728
+ if (c.label && !existing.label) existing.label = c.label;
2729
+ if (!existing.sameAsGroup.includes(c.iri)) existing.sameAsGroup.push(c.iri);
2730
+ } else {
2731
+ merged.set(rep, {
2732
+ iri: rep,
2733
+ label: c.label ?? labels.get(rep),
2734
+ score: c.score,
2735
+ sameAsGroup: [c.iri, ...rep !== c.iri ? [rep] : []]
2736
+ });
2737
+ }
2738
+ }
2739
+ for (const group of merged.values()) {
2740
+ group.label ??= labels.get(group.iri);
2741
+ if (group.sameAsGroup.length < 2) {
2742
+ const members = [...canonical.keys()].filter((m) => canonical.get(m) === canonical.get(group.iri));
2743
+ if (members.length > 1) group.sameAsGroup = members;
2744
+ }
2745
+ }
2746
+ return [...merged.values()].sort((a, b) => b.score - a.score).slice(0, k);
2747
+ }
2748
+ };
2749
+ function stripLiteral(term) {
2750
+ const m = term.match(/^"(.*)"(\^\^<?[\w:/#.-]+>?|@[\w-]+)?$/);
2751
+ return m ? m[1] : term;
2752
+ }
2753
+ function localName(iri) {
2754
+ const tail = iri.split(/[#/:]/).pop() ?? "";
2755
+ return tail.toLowerCase();
2756
+ }
2757
+ function linkEntityTool(linker) {
2758
+ return defineTool({
2759
+ name: "link_entity",
2760
+ description: "Link a text mention to known graph entities (labels + sameAs canonicalized). Returns canonical IRIs with scores \u2014 never invent IRIs.",
2761
+ schema: z10.object({ mention: z10.string().min(1).max(200), k: z10.number().int().min(1).max(10).default(5) }),
2762
+ execute: async ({ mention, k }) => linker.link(mention, k)
2763
+ });
2764
+ }
2765
+ var MappingProposals = class {
2766
+ constructor(opts = {}) {
2767
+ this.opts = opts;
2768
+ }
2769
+ opts;
2770
+ seq = 0;
2771
+ items = /* @__PURE__ */ new Map();
2772
+ /** True when proposals commit themselves (autonomy on AND a store bound). */
2773
+ get autonomy() {
2774
+ return this.opts.autonomy !== false && !!this.opts.store;
2775
+ }
2776
+ async commit(proposal, store) {
2777
+ await store.add([
2778
+ {
2779
+ subject: proposal.to,
2780
+ predicate: "skos:altLabel",
2781
+ object: `"${proposal.from}"`,
2782
+ source: proposal.source,
2783
+ derivedFrom: `mapping:${proposal.id}`
2784
+ }
2785
+ ]);
2786
+ }
2787
+ /**
2788
+ * Record an LLM-proposed mapping. With autonomy (default) + a bound store
2789
+ * it commits right away, provenance required; with `autonomy: false` (or
2790
+ * no store bound) it queues as pending until decide().
2791
+ */
2792
+ async propose(from, to, source) {
2793
+ const existing = [...this.items.values()].find(
2794
+ (p) => p.from === from && p.to === to && (p.status === "pending" || this.autonomy && p.status === "approved")
2795
+ );
2796
+ if (existing) return existing;
2797
+ const proposal = {
2798
+ id: `map-${++this.seq}`,
2799
+ from,
2800
+ to,
2801
+ source,
2802
+ status: "pending",
2803
+ proposedAt: (/* @__PURE__ */ new Date()).toISOString()
2804
+ };
2805
+ if (this.autonomy && this.opts.store) {
2806
+ await this.commit(proposal, this.opts.store);
2807
+ proposal.status = "approved";
2808
+ proposal.committedByAutonomy = true;
2809
+ }
2810
+ this.items.set(proposal.id, proposal);
2811
+ return proposal;
2812
+ }
2813
+ list(status) {
2814
+ return [...this.items.values()].filter((p) => !status || p.status === status);
2815
+ }
2816
+ /** Human decision. `approved` also commits the alias into the graph. */
2817
+ async decide(store, id, decision) {
2818
+ const proposal = this.items.get(id);
2819
+ if (!proposal || proposal.status !== "pending") return void 0;
2820
+ proposal.status = decision;
2821
+ if (decision === "approved") {
2822
+ await this.commit(proposal, store);
2823
+ }
2824
+ return proposal;
2825
+ }
2826
+ };
2827
+ function proposeMappingTool(proposals) {
2828
+ return defineTool({
2829
+ name: "propose_mapping",
2830
+ description: "Map a raw term to a canonical ontology IRI. Autonomy mode commits the alias immediately (provenance required); review mode queues it for human approval.",
2831
+ schema: z10.object({
2832
+ from: z10.string().min(1).max(200),
2833
+ to: z10.string().min(1).max(500),
2834
+ source: z10.string().min(1).describe("Provenance URI for the committed alias")
2835
+ }),
2836
+ execute: async ({ from, to, source }) => {
2837
+ let p;
2838
+ try {
2839
+ p = await proposals.propose(from, to, source);
2840
+ } catch (err) {
2841
+ return `Commit rejected \u2014 fix and retry: ${err instanceof Error ? err.message : String(err)}`;
2842
+ }
2843
+ return p.committedByAutonomy ? { id: p.id, status: "approved", committed: true } : { id: p.id, status: "pending", note: "Review mode: awaits a human decide() before writing." };
2844
+ }
2845
+ });
2846
+ }
2847
+ function resolveIri(term, prefixes) {
2848
+ const m = term.match(/^([a-zA-Z][\w-]*):(.+)$/);
2849
+ const base = m ? prefixes[m[1]] : void 0;
2850
+ if (m && base) return `${base}${m[2]}`;
2851
+ if (/^https?:\/\//.test(term)) return term;
2852
+ return void 0;
2853
+ }
2854
+ function compactIri(iri, prefixes) {
2855
+ for (const [prefix, base] of Object.entries(prefixes)) {
2856
+ if (iri.startsWith(base)) return `${prefix}:${iri.slice(base.length)}`;
2857
+ }
2858
+ return iri;
2859
+ }
2860
+ function termToJsonLd(term, prefixes) {
2861
+ const iri = resolveIri(term, prefixes);
2862
+ if (iri) return { "@id": iri };
2863
+ const lang = term.match(/^"(.*)"@([\w-]+)$/);
2864
+ if (lang) return { "@value": lang[1], "@language": lang[2] };
2865
+ const typed = term.match(/^"(.*)"\^\^<?([\w:/#.-]+)>?$/);
2866
+ if (typed) {
2867
+ const type = resolveIri(typed[2], prefixes) ?? typed[2];
2868
+ return { "@value": typed[1], "@type": type };
2869
+ }
2870
+ const plain = term.match(/^"(.*)"$/);
2871
+ if (plain) return { "@value": plain[1] };
2872
+ if (/^-?\d+(\.\d+)?$/.test(term) || /^(true|false)$/.test(term)) return { "@value": term };
2873
+ return { "@value": term };
2874
+ }
2875
+ function jsonLdToTerm(value, prefixes) {
2876
+ if (value === null || typeof value !== "object") {
2877
+ const native = String(value);
2878
+ return /^-?\d+(\.\d+)?$/.test(native) || native === "true" || native === "false" ? native : `"${native}"`;
2879
+ }
2880
+ if (value["@id"]) return compactIri(value["@id"], prefixes);
2881
+ if (value["@value"] !== void 0) {
2882
+ if (value["@language"]) return `"${value["@value"]}"@${value["@language"]}`;
2883
+ if (value["@type"]) {
2884
+ const type = compactIri(value["@type"], prefixes);
2885
+ return `"${value["@value"]}"^^${type}`;
2886
+ }
2887
+ return `"${value["@value"]}"`;
2888
+ }
2889
+ return void 0;
2890
+ }
2891
+ function quadsToJsonLd(quads, prefixes = JSONLD_PREFIXES) {
2892
+ const byGraph = /* @__PURE__ */ new Map();
2893
+ for (const q of quads) {
2894
+ const g = q.graph ?? "";
2895
+ byGraph.set(g, [...byGraph.get(g) ?? [], q]);
2896
+ }
2897
+ const buildNodes = (group) => {
2898
+ const nodes = /* @__PURE__ */ new Map();
2899
+ const nodeOf = (id) => {
2900
+ let n = nodes.get(id);
2901
+ if (!n) {
2902
+ n = { "@id": resolveIri(id, prefixes) ?? id };
2903
+ nodes.set(id, n);
2904
+ }
2905
+ return n;
2906
+ };
2907
+ for (const q of group) {
2908
+ const node = nodeOf(q.subject);
2909
+ if (q.predicate === "rdf:type" || q.predicate === "a") {
2910
+ const existing2 = node["@type"];
2911
+ const typeIri = resolveIri(q.object, prefixes) ?? q.object;
2912
+ node["@type"] = Array.isArray(existing2) ? [...existing2, typeIri].filter((t, i, a) => a.indexOf(t) === i) : existing2 ? [existing2, typeIri] : typeIri;
2913
+ continue;
2914
+ }
2915
+ const key = resolveIri(q.predicate, prefixes) ?? q.predicate;
2916
+ const value = termToJsonLd(q.object, prefixes);
2917
+ const existing = node[key];
2918
+ node[key] = Array.isArray(existing) ? [...existing, value] : existing ? [existing, value] : value;
2919
+ }
2920
+ return [...nodes.values()];
2921
+ };
2922
+ const graphEntries = [];
2923
+ for (const [g, group] of byGraph) {
2924
+ const nodes = buildNodes(group);
2925
+ if (g === "") graphEntries.push(...nodes);
2926
+ else graphEntries.push({ "@id": resolveIri(g, prefixes) ?? g, "@graph": nodes });
2927
+ }
2928
+ return { "@context": prefixes, "@graph": graphEntries };
2929
+ }
2930
+ function jsonldToQuads(doc, prefixes = JSONLD_PREFIXES) {
2931
+ const out = [];
2932
+ let blank = 0;
2933
+ const convertNode = (node, graphName) => {
2934
+ const subjectId = (node["@id"] ? String(node["@id"]) : "") ? compactIri(String(node["@id"]), prefixes) : `_:b${blank++}`;
2935
+ const types = node["@type"];
2936
+ for (const t of Array.isArray(types) ? types : types ? [types] : []) {
2937
+ out.push({ subject: subjectId, predicate: "rdf:type", object: compactIri(String(t), prefixes), ...graphName ? { graph: graphName } : {} });
2938
+ }
2939
+ for (const [key, raw] of Object.entries(node)) {
2940
+ if (key === "@id" || key === "@type") continue;
2941
+ const predicate = compactIri(key.includes(":") ? resolveIri(key, prefixes) ?? key : key, prefixes);
2942
+ for (const v of Array.isArray(raw) ? raw : [raw]) {
2943
+ const object = jsonLdToTerm(v, prefixes);
2944
+ if (object !== void 0) out.push({ subject: subjectId, predicate, object, ...graphName ? { graph: graphName } : {} });
2945
+ }
2946
+ }
2947
+ };
2948
+ for (const entry of doc["@graph"] ?? []) {
2949
+ if (entry && typeof entry === "object" && Array.isArray(entry["@graph"])) {
2950
+ const named = entry;
2951
+ const graphName = named["@id"] ? compactIri(named["@id"], prefixes) : `_:g${out.length}`;
2952
+ for (const node of named["@graph"]) convertNode(node, graphName);
2953
+ continue;
2954
+ }
2955
+ convertNode(entry, void 0);
2956
+ }
2957
+ return out;
2958
+ }
2959
+
2960
+ // src/semantic/ontology-evolution.ts
2961
+ async function runConformance(store, battery) {
2962
+ const results = [];
2963
+ const runner = async (check, closure2) => {
2964
+ if (check.ask) {
2965
+ const rows = await executeSelect(store, check.ask);
2966
+ const min = check.minRows ?? 1;
2967
+ const ok = rows.length >= min && (check.maxRows == null || rows.length <= check.maxRows);
2968
+ results.push({
2969
+ id: check.id,
2970
+ passed: ok,
2971
+ detail: `${rows.length} rows (expected >= ${min}${check.maxRows == null ? "" : `, <= ${check.maxRows}`})`
2972
+ });
2973
+ return;
2974
+ }
2975
+ if (check.shape) {
2976
+ const scratch = new InMemoryGraphStore({ shapes: [check.shape] });
2977
+ const mine = scratch.checkShapes(closure2).violations;
2978
+ results.push({
2979
+ id: check.id,
2980
+ passed: mine.length === 0,
2981
+ detail: mine.length ? mine.map((v) => v.message).join("; ") : "shape holds"
2982
+ });
2983
+ return;
2984
+ }
2985
+ results.push({
2986
+ id: check.id,
2987
+ passed: false,
2988
+ detail: `check has neither ask nor shape${check.reason ? ` (${check.reason})` : ""}`
2989
+ });
2990
+ };
2991
+ const closure = await store.match(null, null, null);
2992
+ for (const check of battery) {
2993
+ try {
2994
+ await runner(check, closure);
2995
+ } catch (err) {
2996
+ results.push({ id: check.id, passed: false, detail: err instanceof Error ? err.message : String(err) });
2997
+ }
2998
+ }
2999
+ return { passed: results.every((r) => r.passed), results };
3000
+ }
3001
+ function termKey(q) {
3002
+ return `${q.subject}|${q.predicate}|${q.object}`;
3003
+ }
3004
+ var TBOX_PREDICATES = /* @__PURE__ */ new Set([
3005
+ "rdfs:subClassOf",
3006
+ "http://www.w3.org/2000/01/rdf-schema#subClassOf",
3007
+ "rdfs:subPropertyOf",
3008
+ "http://www.w3.org/2000/01/rdf-schema#subPropertyOf",
3009
+ "rdfs:domain",
3010
+ "http://www.w3.org/2000/01/rdf-schema#domain",
3011
+ "rdfs:range",
3012
+ "http://www.w3.org/2000/01/rdf-schema#range",
3013
+ "owl:inverseOf",
3014
+ "http://www.w3.org/2002/07/owl#inverseOf"
3015
+ ]);
3016
+ var OWL_VOCAB = /* @__PURE__ */ new Set([
3017
+ "owl:TransitiveProperty",
3018
+ "http://www.w3.org/2002/07/owl#TransitiveProperty",
3019
+ "owl:SymmetricProperty",
3020
+ "http://www.w3.org/2002/07/owl#SymmetricProperty",
3021
+ "owl:FunctionalProperty",
3022
+ "http://www.w3.org/2002/07/owl#FunctionalProperty",
3023
+ "owl:Class",
3024
+ "http://www.w3.org/2002/07/owl#Class",
3025
+ "rdfs:Class",
3026
+ "http://www.w3.org/2000/01/rdf-schema#Class",
3027
+ "rdfs:Datatype",
3028
+ "http://www.w3.org/2000/01/rdf-schema#Datatype"
3029
+ ]);
3030
+ async function migrateTBox(oldStore, spec, opts = {}) {
3031
+ const report = {
3032
+ from: spec.from,
3033
+ to: spec.to,
3034
+ status: "migrated",
3035
+ addedQuads: 0,
3036
+ removedQuads: 0,
3037
+ rewrittenQuads: 0,
3038
+ deprecatedWarnings: [],
3039
+ errors: []
3040
+ };
3041
+ if (opts.battery?.length) {
3042
+ report.preBattery = await runConformance(oldStore, opts.battery);
3043
+ if (!report.preBattery.passed) {
3044
+ report.status = "aborted";
3045
+ report.errors.push("pre-migration conformance battery failed \u2014 ontology not migrated");
3046
+ return { report };
3047
+ }
3048
+ }
3049
+ const removeKeys = new Set((spec.removed ?? []).map(termKey));
3050
+ const renameMap = /* @__PURE__ */ new Map();
3051
+ for (const r of spec.renames ?? []) renameMap.set(r.from, r.to);
3052
+ for (const d of spec.deprecations ?? []) {
3053
+ if (d.replacement) {
3054
+ renameMap.set(d.term, d.replacement);
3055
+ report.deprecatedWarnings.push(`${d.term} deprecated (${d.reason}) \u2014 rewritten to ${d.replacement}`);
3056
+ } else {
3057
+ report.deprecatedWarnings.push(`${d.term} deprecated (${d.reason}) \u2014 uses kept, flagged`);
3058
+ }
3059
+ }
3060
+ const rewriteTerm = (term) => renameMap.get(term) ?? term;
3061
+ const factory = opts.factory ?? ((o) => new InMemoryGraphStore(o));
3062
+ const newStore = factory({
3063
+ shapes: spec.shapes ?? oldStore.ontologyShapes(),
3064
+ rules: spec.rules ?? oldStore.ontologyRules()
3065
+ });
3066
+ const explicit = (await oldStore.match(null, null, null)).filter((q) => q.source !== "entailment");
3067
+ const tboxGraph = opts.tboxGraph === false ? void 0 : opts.tboxGraph ?? `urn:fabisch:ontology:${spec.to}`;
3068
+ report.tboxGraph = tboxGraph;
3069
+ const isAxiom = (q, predicatesInUse2) => {
3070
+ if (TBOX_PREDICATES.has(q.predicate)) return true;
3071
+ if (q.predicate === "rdf:type" || q.predicate === "a" || q.predicate === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type") {
3072
+ return predicatesInUse2.has(q.subject) || OWL_VOCAB.has(q.object);
3073
+ }
3074
+ return false;
3075
+ };
3076
+ const predicatesInUse = new Set(explicit.filter((q) => !removeKeys.has(termKey(q))).map((q) => rewriteTerm(q.predicate)));
3077
+ const rewrittenSpecAdded = (spec.added ?? []).map(
3078
+ (aq) => tboxGraph ? { ...aq, graph: tboxGraph, derivedFrom: `migration:${spec.from}->${spec.to}` } : { ...aq, derivedFrom: `migration:${spec.from}->${spec.to}` }
3079
+ );
3080
+ const rebuilt = [];
3081
+ for (const q of explicit) {
3082
+ if (removeKeys.has(termKey(q))) {
3083
+ report.removedQuads += 1;
3084
+ continue;
3085
+ }
3086
+ const next = {
3087
+ subject: rewriteTerm(q.subject),
3088
+ predicate: rewriteTerm(q.predicate),
3089
+ object: rewriteTerm(q.object),
3090
+ source: q.source,
3091
+ ...q.graph ? { graph: q.graph } : {},
3092
+ ...q.derivedFrom ? { derivedFrom: q.derivedFrom } : {}
3093
+ };
3094
+ if (isAxiom(next, predicatesInUse) && tboxGraph) next.graph = tboxGraph;
3095
+ if (termKey(next) !== termKey(q)) {
3096
+ report.rewrittenQuads += 1;
3097
+ next.derivedFrom = `${next.derivedFrom ? `${next.derivedFrom};` : ""}migration:${spec.from}->${spec.to}`;
3098
+ }
3099
+ rebuilt.push(next);
3100
+ }
3101
+ for (const aq of rewrittenSpecAdded) rebuilt.push(aq);
3102
+ report.addedQuads = (spec.added ?? []).length;
3103
+ try {
3104
+ await newStore.add(rebuilt);
3105
+ } catch (err) {
3106
+ report.status = "failed";
3107
+ report.errors.push(`v4 store rejected migrated facts: ${err instanceof Error ? err.message : String(err)}`);
3108
+ return { report };
3109
+ }
3110
+ if (opts.battery?.length) {
3111
+ report.postBattery = await runConformance(newStore, opts.battery);
3112
+ if (!report.postBattery.passed) {
3113
+ report.status = "failed";
3114
+ const failed = report.postBattery.results.filter((r) => !r.passed).map((r) => `${r.id}: ${r.detail}`);
3115
+ report.errors.push(`post-migration conformance battery failed \u2014 ${failed.join("; ")}`);
3116
+ return { report };
3117
+ }
3118
+ }
3119
+ return { report, store: newStore };
3120
+ }
3121
+
3122
+ // src/semantic/ontology-review.ts
3123
+ import { z as z11 } from "zod";
3124
+ var RDF_TYPE = /* @__PURE__ */ new Set(["rdf:type", "a", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"]);
3125
+ var isAxiomQuad = (q, predicatesInUse) => TBOX_PREDICATES.has(q.predicate) || RDF_TYPE.has(q.predicate) && (predicatesInUse.has(q.subject) || OWL_VOCAB.has(q.object));
3126
+ function strip(term) {
3127
+ const m = term.match(/^"(.*)"(\^\^<?[\w:/#.-]+>?|@[\w-]+)?$/);
3128
+ return m ? m[1] : term;
3129
+ }
3130
+ async function reviewOntology(store) {
3131
+ const closure = await store.match(null, null, null);
3132
+ const explicit = closure.filter((q) => q.source !== "entailment");
3133
+ const predicatesInUse = new Set(explicit.map((q) => q.predicate));
3134
+ const labels = /* @__PURE__ */ new Map();
3135
+ for (const q of closure) {
3136
+ if (LABEL_PREDICATES.has(q.predicate)) labels.set(q.subject, strip(q.object));
3137
+ }
3138
+ const classes = /* @__PURE__ */ new Map();
3139
+ const properties = /* @__PURE__ */ new Map();
3140
+ const class_ = (term) => {
3141
+ let s = classes.get(term);
3142
+ if (!s) {
3143
+ s = { term, kind: "class", uses: 0, axiomCount: 0, label: labels.get(term) };
3144
+ classes.set(term, s);
3145
+ }
3146
+ return s;
3147
+ };
3148
+ const property = (term) => {
3149
+ let s = properties.get(term);
3150
+ if (!s) {
3151
+ s = { term, kind: "property", uses: 0, axiomCount: 0, label: labels.get(term) };
3152
+ properties.set(term, s);
3153
+ }
3154
+ return s;
3155
+ };
3156
+ const domains = /* @__PURE__ */ new Map();
3157
+ const ranges = /* @__PURE__ */ new Map();
3158
+ for (const q of explicit) {
3159
+ const axiom = isAxiomQuad(q, predicatesInUse);
3160
+ if (RDF_TYPE.has(q.predicate)) {
3161
+ if (axiom) class_(q.subject).axiomCount += 1;
3162
+ else class_(q.object).uses += 1;
3163
+ continue;
3164
+ }
3165
+ if (TBOX_PREDICATES.has(q.predicate)) {
3166
+ const subject = q.predicate.includes("subClassOf") ? class_(q.subject) : property(q.subject);
3167
+ subject.axiomCount += 1;
3168
+ if (!q.predicate.includes("inverseOf")) class_(q.object).axiomCount += 1;
3169
+ if (q.predicate.includes("domain")) domains.set(q.subject, (domains.get(q.subject) ?? 0) + 1);
3170
+ if (q.predicate.includes("range")) ranges.set(q.subject, (ranges.get(q.subject) ?? 0) + 1);
3171
+ continue;
3172
+ }
3173
+ property(q.predicate).uses += 1;
3174
+ }
3175
+ const all = [...classes.values(), ...properties.values()];
3176
+ const undeclared = all.filter((t) => t.uses > 0 && t.axiomCount === 0);
3177
+ const unused = all.filter((t) => t.axiomCount > 0 && t.uses === 0).map((t) => t.term);
3178
+ const unlabeled = all.filter((t) => !t.label).map((t) => t.term);
3179
+ const missingDomain = [...properties.values()].filter((t) => t.axiomCount > 0 || t.uses > 0).filter((t) => !domains.has(t.term)).map((t) => t.term);
3180
+ const missingRange = [...properties.values()].filter((t) => t.axiomCount > 0 || t.uses > 0).filter((t) => !ranges.has(t.term)).map((t) => t.term);
3181
+ return {
3182
+ classes: [...classes.values()].sort((a, b) => b.uses - a.uses || a.term.localeCompare(b.term)),
3183
+ properties: [...properties.values()].sort((a, b) => b.uses - a.uses || a.term.localeCompare(b.term)),
3184
+ undeclared: undeclared.sort((a, b) => b.uses - a.uses),
3185
+ unused,
3186
+ unlabeled,
3187
+ missingDomain,
3188
+ missingRange,
3189
+ explicitQuads: explicit.length,
3190
+ closureQuads: closure.length
3191
+ };
3192
+ }
3193
+ async function proposeShapes(store, opts = {}) {
3194
+ const minInstances = opts.minInstances ?? 2;
3195
+ const coverage = opts.coverage ?? 0.5;
3196
+ const explicit = (await store.match(null, null, null)).filter((q) => q.source !== "entailment");
3197
+ const predicatesInUse = new Set(explicit.map((q) => q.predicate));
3198
+ const instancesOf = /* @__PURE__ */ new Map();
3199
+ for (const q of explicit) {
3200
+ if (!RDF_TYPE.has(q.predicate) || isAxiomQuad(q, predicatesInUse)) continue;
3201
+ const set = instancesOf.get(q.object) ?? /* @__PURE__ */ new Set();
3202
+ set.add(q.subject);
3203
+ instancesOf.set(q.object, set);
3204
+ }
3205
+ const shapes = [];
3206
+ for (const [cls, subjects] of instancesOf) {
3207
+ if (subjects.size < minInstances) continue;
3208
+ const observed = /* @__PURE__ */ new Map();
3209
+ const bySubject = /* @__PURE__ */ new Map();
3210
+ for (const q of explicit) {
3211
+ if (!subjects.has(q.subject)) continue;
3212
+ bySubject.set(q.subject, [...bySubject.get(q.subject) ?? [], q]);
3213
+ }
3214
+ for (const [, quads] of bySubject) {
3215
+ const byPath = /* @__PURE__ */ new Map();
3216
+ for (const q of quads) {
3217
+ if (RDF_TYPE.has(q.predicate)) continue;
3218
+ byPath.set(q.predicate, [...byPath.get(q.predicate) ?? [], q]);
3219
+ }
3220
+ for (const [path, pathQuads] of byPath) {
3221
+ const o = observed.get(path) ?? { present: 0, maxSeen: 0, datatypes: /* @__PURE__ */ new Set(), nodeKinds: /* @__PURE__ */ new Set() };
3222
+ o.present += 1;
3223
+ o.maxSeen = Math.max(o.maxSeen, pathQuads.length);
3224
+ for (const q of pathQuads) {
3225
+ o.nodeKinds.add(isLiteralTerm(q.object) ? "literal" : "iri");
3226
+ const dt = datatypeOf(q.object);
3227
+ if (dt) o.datatypes.add(dt);
3228
+ }
3229
+ observed.set(path, o);
3230
+ }
3231
+ }
3232
+ const properties = [];
3233
+ for (const [path, o] of observed) {
3234
+ if (o.present / subjects.size < coverage) continue;
3235
+ const shape = { path };
3236
+ if (o.present === subjects.size) shape.minCount = 1;
3237
+ if (o.maxSeen === 1) shape.maxCount = 1;
3238
+ if (o.datatypes.size === 1) shape.datatype = [...o.datatypes][0];
3239
+ if (o.nodeKinds.size === 1) shape.nodeKind = [...o.nodeKinds][0];
3240
+ properties.push(shape);
3241
+ }
3242
+ if (properties.length) shapes.push({ targetClass: cls, properties });
3243
+ }
3244
+ return shapes;
3245
+ }
3246
+ function isLiteralTerm(term) {
3247
+ if (term.startsWith('"')) return true;
3248
+ if (/^(-?\d+(\.\d+)?|true|false)$/.test(term)) return true;
3249
+ if (term.startsWith("<") || /^[a-zA-Z][\w-]*:/.test(term)) return false;
3250
+ return true;
3251
+ }
3252
+ function datatypeOf(term) {
3253
+ const m = term.match(/\^\^<?([\w:/#.-]+)>?$/);
3254
+ if (m) return m[1];
3255
+ if (/^".*"@[\w-]+$/.test(term)) return "rdf:langString";
3256
+ if (/^".*"$/.test(term)) return "xsd:string";
3257
+ if (/^-?\d+$/.test(term)) return "xsd:integer";
3258
+ if (/^-?\d*\.\d+$/.test(term)) return "xsd:decimal";
3259
+ if (/^(true|false)$/.test(term)) return "xsd:boolean";
3260
+ return void 0;
3261
+ }
3262
+ async function adoptShapes(store, shapes) {
3263
+ const closure = await store.match(null, null, null);
3264
+ const adopted = [];
3265
+ const rejected = [];
3266
+ for (const shape of shapes) {
3267
+ const violations = new InMemoryGraphStore({ shapes: [shape] }).checkShapes(closure).violations;
3268
+ if (violations.length) rejected.push({ shape, violations: violations.map((v) => v.message) });
3269
+ else adopted.push(shape);
3270
+ }
3271
+ if (adopted.length) store.addShapes(...adopted);
3272
+ return { adopted, rejected };
3273
+ }
3274
+ function reviewOntologyTool(store) {
3275
+ return defineTool({
3276
+ name: "review_ontology",
3277
+ description: "Audit the knowledge graph's ontology: classes/properties with usage counts, used-but-undeclared terms, unlabeled/unused terms, properties missing domain or range. Read-only.",
3278
+ schema: z11.object({}),
3279
+ execute: async () => reviewOntology(store)
3280
+ });
3281
+ }
3282
+ function proposeShapesTool(store, opts = {}) {
3283
+ return defineTool({
3284
+ name: "propose_shapes",
3285
+ description: "Induce candidate SHACL shapes from existing instance data (>= 2 instances per class, >= 50% property coverage). PROPOSAL: adoption is the builder's call via adoptShapes(), never automatic.",
3286
+ schema: z11.object({}),
3287
+ execute: async () => proposeShapes(store, opts)
3288
+ });
3289
+ }
3290
+
3291
+ // src/semantic/index.ts
3292
+ var RDF_TYPE2 = /* @__PURE__ */ new Set(["rdf:type", "a", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"]);
3293
+ var SUB_CLASS = /* @__PURE__ */ new Set(["rdfs:subClassOf", "http://www.w3.org/2000/01/rdf-schema#subClassOf"]);
3294
+ var SUB_PROP = /* @__PURE__ */ new Set(["rdfs:subPropertyOf", "http://www.w3.org/2000/01/rdf-schema#subPropertyOf"]);
3295
+ var DOMAIN = /* @__PURE__ */ new Set(["rdfs:domain", "http://www.w3.org/2000/01/rdf-schema#domain"]);
3296
+ var RANGE = /* @__PURE__ */ new Set(["rdfs:range", "http://www.w3.org/2000/01/rdf-schema#range"]);
3297
+ var OWL_TRANSITIVE = /* @__PURE__ */ new Set(["owl:TransitiveProperty", "http://www.w3.org/2002/07/owl#TransitiveProperty"]);
3298
+ var OWL_SYMMETRIC = /* @__PURE__ */ new Set(["owl:SymmetricProperty", "http://www.w3.org/2002/07/owl#SymmetricProperty"]);
3299
+ var OWL_FUNCTIONAL = /* @__PURE__ */ new Set(["owl:FunctionalProperty", "http://www.w3.org/2002/07/owl#FunctionalProperty"]);
3300
+ var OWL_INVERSE = /* @__PURE__ */ new Set(["owl:inverseOf", "http://www.w3.org/2002/07/owl#inverseOf"]);
3301
+ function isLiteral(term) {
3302
+ if (term.startsWith('"')) return true;
3303
+ if (/^(-?\d+(\.\d+)?|true|false)$/.test(term)) return true;
3304
+ if (term.startsWith("<") || /^[a-zA-Z][\w-]*:/.test(term)) return false;
3305
+ return true;
3306
+ }
3307
+ function termDatatype(term) {
3308
+ const m = term.match(/\^\^<?([\w:/#.-]+)>?$/);
3309
+ if (m) return m[1];
3310
+ if (/^".*"@[\w-]+$/.test(term)) return "rdf:langString";
3311
+ if (/^".*"$/.test(term)) return "xsd:string";
3312
+ if (/^-?\d+$/.test(term)) return "xsd:integer";
3313
+ if (/^-?\d*\.\d+$/.test(term)) return "xsd:decimal";
3314
+ if (/^(true|false)$/.test(term)) return "xsd:boolean";
3315
+ return void 0;
3316
+ }
3317
+ var InMemoryGraphStore = class {
3318
+ quads = [];
3319
+ derivations = /* @__PURE__ */ new Map();
3320
+ shapes = [];
3321
+ rules = [];
3322
+ constructor(opts = {}) {
3323
+ this.shapes = [...opts.shapes ?? []];
3324
+ this.rules = [...opts.rules ?? []];
3325
+ }
3326
+ addShapes(...shapes) {
3327
+ this.shapes.push(...shapes);
3328
+ return this;
3329
+ }
3330
+ addRules(...rules) {
3331
+ this.rules.push(...rules);
3332
+ return this;
3333
+ }
3334
+ /** Registered SHACL shapes (carried to new stores by TBox migrations). */
3335
+ ontologyShapes() {
3336
+ return [...this.shapes];
3337
+ }
3338
+ /** Registered CONSTRUCT rules (versioned with the ontology they encode). */
3339
+ ontologyRules() {
3340
+ return [...this.rules];
3341
+ }
3342
+ /** Identity includes the named graph: the same s/p/o in two graphs are two quads. */
3343
+ key(q) {
3344
+ return `${q.subject}|${q.predicate}|${q.object}|${q.graph ?? ""}`;
3345
+ }
3346
+ async add(quads) {
3347
+ const report = await this.validate(quads);
3348
+ if (!report.valid) {
3349
+ throw new Error(`SHACL violation: ${report.violations.map((v) => v.message).join("; ")}`);
3350
+ }
3351
+ const known = new Set(this.quads.map((q) => this.key(q)));
3352
+ const addedKeys = [];
3353
+ for (const q of quads) {
3354
+ if (!known.has(this.key(q))) {
3355
+ known.add(this.key(q));
3356
+ addedKeys.push(this.key(q));
3357
+ this.quads.push(q);
3358
+ }
3359
+ }
3360
+ const rollback = () => {
3361
+ const doomed = new Set(addedKeys);
3362
+ this.quads = this.quads.filter((q) => !doomed.has(this.key(q)));
3363
+ for (const k of doomed) this.derivations.delete(k);
3364
+ };
3365
+ const conflicts = this.checkFunctional();
3366
+ if (conflicts.length) {
3367
+ rollback();
3368
+ throw new Error(`Functional property violation: ${conflicts.join("; ")}`);
3369
+ }
3370
+ const shapeReport = this.checkShapes(this.quads);
3371
+ if (!shapeReport.valid) {
3372
+ rollback();
3373
+ throw new Error(`SHACL violation: ${shapeReport.violations.map((v) => v.message).join("; ")}`);
3374
+ }
3375
+ this.entailToFixpoint(known);
3376
+ }
3377
+ async match(s, p, o, g) {
3378
+ return this.quads.filter(
3379
+ (q) => (!s || q.subject === s) && (!p || q.predicate === p) && (!o || q.object === o) && (!g || q.graph === g)
3380
+ );
3381
+ }
3382
+ /** Structural + provenance check. Full SHACL engine lands in P1-sem. */
3383
+ async validate(quads) {
3384
+ const violations = quads.filter((q) => !q.subject || !q.predicate || !q.object).map((q) => ({ message: `Incomplete triple: ${JSON.stringify(q)}` }));
3385
+ const noSource = quads.filter((q) => !q.source).map((q) => ({ message: `Missing provenance (source) on ${q.subject} ${q.predicate} ${q.object}` }));
3386
+ return { valid: violations.length + noSource.length === 0, violations: [...violations, ...noSource] };
3387
+ }
3388
+ async why(quad) {
3389
+ return this.derivations.get(this.key(quad)) ?? null;
3390
+ }
3391
+ /** Functional properties must have at most one value per subject (conflict, not a second fact). */
3392
+ checkFunctional() {
3393
+ const functionalProps = new Set(
3394
+ this.quads.filter((q) => RDF_TYPE2.has(q.predicate) && OWL_FUNCTIONAL.has(q.object)).map((q) => q.subject)
3395
+ );
3396
+ const conflicts = [];
3397
+ for (const prop of functionalProps) {
3398
+ const bySubject = /* @__PURE__ */ new Map();
3399
+ for (const q of this.quads) {
3400
+ if (q.predicate !== prop) continue;
3401
+ const values = bySubject.get(q.subject) ?? /* @__PURE__ */ new Set();
3402
+ values.add(q.object);
3403
+ bySubject.set(q.subject, values);
3404
+ }
3405
+ for (const [subject, values] of bySubject) {
3406
+ if (values.size > 1) conflicts.push(`${subject} has ${values.size} values for functional ${prop}`);
3407
+ }
3408
+ }
3409
+ return conflicts;
3410
+ }
3411
+ /** SHACL-subset validation over a quad set. */
3412
+ checkShapes(quads) {
3413
+ const violations = [];
3414
+ for (const shape of this.shapes) {
3415
+ const targets = quads.filter((q) => RDF_TYPE2.has(q.predicate) && q.object === shape.targetClass).map((q) => q.subject);
3416
+ for (const node of new Set(targets)) {
3417
+ for (const prop of shape.properties) {
3418
+ const values = quads.filter((q) => q.subject === node && q.predicate === prop.path).map((q) => q.object);
3419
+ if (prop.minCount != null && values.length < prop.minCount) {
3420
+ violations.push({ message: `${node} needs >= ${prop.minCount} ${prop.path}`, node, path: prop.path });
3421
+ }
3422
+ if (prop.maxCount != null && values.length > prop.maxCount) {
3423
+ violations.push({ message: `${node} allows <= ${prop.maxCount} ${prop.path}`, node, path: prop.path });
3424
+ }
3425
+ for (const v of values) {
3426
+ if (prop.datatype && termDatatype(v) !== prop.datatype && !(prop.datatype === "xsd:string" && isLiteral(v))) {
3427
+ violations.push({ message: `${node} ${prop.path}=${v} is not ${prop.datatype}`, node, path: prop.path });
3428
+ }
3429
+ if (prop.nodeKind === "iri" && isLiteral(v)) {
3430
+ violations.push({ message: `${node} ${prop.path}=${v} must be an IRI`, node, path: prop.path });
3431
+ }
3432
+ if (prop.nodeKind === "literal" && !isLiteral(v)) {
3433
+ violations.push({ message: `${node} ${prop.path}=${v} must be a literal`, node, path: prop.path });
3434
+ }
3435
+ if (prop.hasValue !== void 0 && v !== prop.hasValue) {
3436
+ violations.push({ message: `${node} ${prop.path} must be ${prop.hasValue}`, node, path: prop.path });
3437
+ }
3438
+ }
3439
+ }
3440
+ }
3441
+ }
3442
+ return { valid: violations.length === 0, violations };
3443
+ }
3444
+ insert(q, premises, rule, known) {
3445
+ const k = this.key(q);
3446
+ if (known.has(k)) return false;
3447
+ known.add(k);
3448
+ this.quads.push(q);
3449
+ this.derivations.set(k, { premises, rule });
3450
+ return true;
3451
+ }
3452
+ /**
3453
+ * RDFS subset + OWL property axioms + versioned rules, to fixpoint:
3454
+ * - RDFS-SUB-TRANS / RDFS-TYPE / RDFS-SUBPROP-TRANS / RDFS-SUBPROP / RDFS-DOMAIN / RDFS-RANGE
3455
+ * - OWL-TRANSITIVE / OWL-SYMMETRIC / OWL-INVERSE (inverseOf both directions)
3456
+ * - <ruleId>@<version> for each applied GraphRule (premises tracked for why())
3457
+ */
3458
+ entailToFixpoint(known) {
3459
+ for (; ; ) {
3460
+ let added = false;
3461
+ const snapshot = [...this.quads];
3462
+ const byPred = (ps) => snapshot.filter((q) => ps.has(q.predicate));
3463
+ const insert = (q, premises, rule) => {
3464
+ const addedOne = this.insert(q, premises, rule, known);
3465
+ added = addedOne || added;
3466
+ return addedOne;
3467
+ };
3468
+ for (const ab of byPred(SUB_CLASS)) {
3469
+ for (const bc of byPred(SUB_CLASS).filter((q) => q.subject === ab.object)) {
3470
+ added = this.insert(
3471
+ { subject: ab.subject, predicate: ab.predicate, object: bc.object, source: "entailment", rule: "RDFS-SUB-TRANS" },
3472
+ [ab, bc],
3473
+ "RDFS-SUB-TRANS",
3474
+ known
3475
+ ) || added;
3476
+ }
3477
+ }
3478
+ for (const t of byPred(RDF_TYPE2)) {
3479
+ for (const sc of byPred(SUB_CLASS).filter((q) => q.subject === t.object)) {
3480
+ added = this.insert(
3481
+ { subject: t.subject, predicate: t.predicate, object: sc.object, source: "entailment", rule: "RDFS-TYPE" },
3482
+ [t, sc],
3483
+ "RDFS-TYPE",
3484
+ known
3485
+ ) || added;
3486
+ }
3487
+ }
3488
+ for (const ab of byPred(SUB_PROP)) {
3489
+ for (const bc of byPred(SUB_PROP).filter((q) => q.subject === ab.object)) {
3490
+ added = this.insert(
3491
+ { subject: ab.subject, predicate: ab.predicate, object: bc.object, source: "entailment", rule: "RDFS-SUBPROP-TRANS" },
3492
+ [ab, bc],
3493
+ "RDFS-SUBPROP-TRANS",
3494
+ known
3495
+ ) || added;
3496
+ }
3497
+ for (const use of snapshot.filter((q) => q.predicate === ab.subject)) {
3498
+ added = this.insert(
3499
+ { subject: use.subject, predicate: ab.object, object: use.object, source: "entailment", rule: "RDFS-SUBPROP" },
3500
+ [ab, use],
3501
+ "RDFS-SUBPROP",
3502
+ known
3503
+ ) || added;
3504
+ }
3505
+ }
3506
+ for (const d of byPred(DOMAIN)) {
3507
+ for (const use of snapshot.filter((q) => q.predicate === d.subject)) {
3508
+ added = this.insert(
3509
+ { subject: use.subject, predicate: "rdf:type", object: d.object, source: "entailment", rule: "RDFS-DOMAIN" },
3510
+ [d, use],
3511
+ "RDFS-DOMAIN",
3512
+ known
3513
+ ) || added;
3514
+ }
3515
+ }
3516
+ for (const r of byPred(RANGE)) {
3517
+ for (const use of snapshot.filter((q) => q.predicate === r.subject)) {
3518
+ if (insert(
3519
+ { subject: use.object, predicate: "rdf:type", object: r.object, source: "entailment", rule: "RDFS-RANGE" },
3520
+ [r, use],
3521
+ "RDFS-RANGE"
3522
+ )) added = true;
3523
+ }
3524
+ }
3525
+ added = this.applyOwl(snapshot, insert) || added;
3526
+ added = this.applyRules(snapshot, insert) || added;
3527
+ if (!added) return;
3528
+ }
3529
+ }
3530
+ applyOwl(snapshot, insert) {
3531
+ let added = false;
3532
+ const transitive = new Set(
3533
+ snapshot.filter((q) => RDF_TYPE2.has(q.predicate) && OWL_TRANSITIVE.has(q.object)).map((q) => q.subject)
3534
+ );
3535
+ const symmetric = new Set(
3536
+ snapshot.filter((q) => RDF_TYPE2.has(q.predicate) && OWL_SYMMETRIC.has(q.object)).map((q) => q.subject)
3537
+ );
3538
+ for (const p of transitive) {
3539
+ const uses = snapshot.filter((q) => q.predicate === p);
3540
+ for (const xy of uses) {
3541
+ for (const yz of uses.filter((q) => q.subject === xy.object)) {
3542
+ if (insert({ subject: xy.subject, predicate: p, object: yz.object, source: "entailment", rule: "OWL-TRANSITIVE" }, [xy, yz], "OWL-TRANSITIVE")) added = true;
3543
+ }
3544
+ }
3545
+ }
3546
+ for (const p of symmetric) {
3547
+ for (const xy of snapshot.filter((q) => q.predicate === p)) {
3548
+ if (insert({ subject: xy.object, predicate: p, object: xy.subject, source: "entailment", rule: "OWL-SYMMETRIC" }, [xy], "OWL-SYMMETRIC")) added = true;
3549
+ }
3550
+ }
3551
+ for (const inv of snapshot.filter((q) => OWL_INVERSE.has(q.predicate))) {
3552
+ for (const use of snapshot.filter((q) => q.predicate === inv.subject || q.predicate === inv.object)) {
3553
+ const flipped = use.predicate === inv.subject ? inv.object : inv.subject;
3554
+ if (insert({ subject: use.object, predicate: flipped, object: use.subject, source: "entailment", rule: "OWL-INVERSE" }, [inv, use], "OWL-INVERSE")) added = true;
3555
+ }
3556
+ }
3557
+ return added;
3558
+ }
3559
+ applyRules(snapshot, insert) {
3560
+ let added = false;
3561
+ for (const rule of this.rules) {
3562
+ const tag = rule.version ? `${rule.id}@${rule.version}` : rule.id;
3563
+ for (const binding of matchPatterns(rule.where, snapshot)) {
3564
+ for (const pattern of rule.construct) {
3565
+ const built = {
3566
+ subject: instantiate(pattern.subject, binding.vars),
3567
+ predicate: instantiate(pattern.predicate, binding.vars),
3568
+ object: instantiate(pattern.object, binding.vars)
3569
+ };
3570
+ if (built.subject == null || built.predicate == null || built.object == null) continue;
3571
+ if (insert(
3572
+ { subject: built.subject, predicate: built.predicate, object: built.object, source: "entailment", rule: tag, derivedFrom: tag },
3573
+ binding.quads,
3574
+ tag
3575
+ )) added = true;
3576
+ }
3577
+ }
3578
+ }
3579
+ return added;
3580
+ }
3581
+ };
3582
+ function isVar(term) {
3583
+ return term.startsWith("?");
3584
+ }
3585
+ function matchTerm(pattern, value, vars) {
3586
+ if (isVar(pattern)) {
3587
+ const bound = vars.get(pattern);
3588
+ if (bound === void 0) {
3589
+ const next = new Map(vars);
3590
+ next.set(pattern, value);
3591
+ return next;
3592
+ }
3593
+ return bound === value ? vars : void 0;
3594
+ }
3595
+ return pattern === value ? vars : void 0;
3596
+ }
3597
+ function matchOne(pattern, quad, vars) {
3598
+ let v = matchTerm(pattern.subject, quad.subject, vars);
3599
+ if (!v) return void 0;
3600
+ v = matchTerm(pattern.predicate, quad.predicate, v);
3601
+ if (!v) return void 0;
3602
+ return matchTerm(pattern.object, quad.object, v);
3603
+ }
3604
+ function matchPatterns(patterns, quads) {
3605
+ let bindings = [{ vars: /* @__PURE__ */ new Map(), quads: [] }];
3606
+ for (const pattern of patterns) {
3607
+ const next = [];
3608
+ for (const b of bindings) {
3609
+ for (const q of quads) {
3610
+ const vars = matchOne(pattern, q, b.vars);
3611
+ if (vars) next.push({ vars, quads: [...b.quads, q] });
3612
+ }
3613
+ }
3614
+ bindings = next;
3615
+ if (!bindings.length) break;
3616
+ }
3617
+ return bindings;
3618
+ }
3619
+ function instantiate(term, vars) {
3620
+ return isVar(term) ? vars.get(term) ?? term : term;
3621
+ }
3622
+ function tokenizeSparql(input) {
3623
+ const tokens = [];
3624
+ const re = /"[^"]*"(?:\^\^<?[\w:/#.-]+>?|@[\w-]+)?|<[^>]*>|[{}().;]|\S+/g;
3625
+ let m;
3626
+ while ((m = re.exec(input)) !== null) tokens.push(m[0]);
3627
+ return tokens;
3628
+ }
3629
+ function parseSelect(query) {
3630
+ const tokens = tokenizeSparql(query);
3631
+ let i = 0;
3632
+ const expect = (want) => {
3633
+ if (tokens[i]?.toUpperCase() !== want) throw new Error(`SPARQL: expected ${want}, got ${tokens[i] ?? "end"}`);
3634
+ i += 1;
3635
+ };
3636
+ expect("SELECT");
3637
+ const variables = [];
3638
+ while (i < tokens.length && tokens[i].startsWith("?")) {
3639
+ variables.push(tokens[i]);
3640
+ i += 1;
3641
+ }
3642
+ if (!variables.length) throw new Error("SPARQL: SELECT needs at least one variable");
3643
+ expect("WHERE");
3644
+ expect("{");
3645
+ const patterns = [];
3646
+ while (i < tokens.length && tokens[i] !== "}") {
3647
+ const s = tokens[i++];
3648
+ const p = tokens[i++];
3649
+ const o = tokens[i++];
3650
+ if (s == null || p == null || o == null || s === "." || p === "." || o === ".") {
3651
+ throw new Error("SPARQL: malformed triple pattern");
3652
+ }
3653
+ patterns.push({ subject: s, predicate: p, object: o });
3654
+ if (tokens[i] === ".") i += 1;
3655
+ }
3656
+ expect("}");
3657
+ return { variables, patterns };
3658
+ }
3659
+ async function executeSelect(store, query) {
3660
+ const { variables, patterns } = parseSelect(query);
3661
+ const quads = await store.match(null, null, null);
3662
+ return matchPatterns(patterns, quads).map((b) => {
3663
+ const row = {};
3664
+ for (const v of variables) row[v] = b.vars.get(v) ?? "";
3665
+ return row;
3666
+ });
3667
+ }
3668
+ function sparqlSelectTool(store) {
3669
+ return defineTool({
3670
+ name: "sparql_select",
3671
+ description: "Query the knowledge graph. Subset: SELECT ?vars WHERE { s p o . ... } over the inferred closure.",
3672
+ schema: z12.object({ query: z12.string().min(1).max(4e3) }),
3673
+ execute: async ({ query }) => executeSelect(store, query)
3674
+ });
3675
+ }
3676
+ function ontologyLookupTool(store) {
3677
+ return defineTool({
3678
+ name: "ontology_lookup",
3679
+ description: "Look up ontology terms (classes, properties, labels) instead of inventing predicates.",
3680
+ schema: z12.object({ term: z12.string().min(1).max(200), limit: z12.number().int().min(1).max(20).default(8) }),
3681
+ execute: async ({ term, limit }) => {
3682
+ const needle = term.toLowerCase();
3683
+ const quads = await store.match(null, null, null);
3684
+ return quads.filter((q) => `${q.subject} ${q.predicate} ${q.object}`.toLowerCase().includes(needle)).slice(0, limit ?? 8).map((q) => ({ subject: q.subject, predicate: q.predicate, object: q.object }));
3685
+ }
3686
+ });
3687
+ }
3688
+ var TurtleQuadSchema = z12.object({
3689
+ subject: z12.string().min(1),
3690
+ predicate: z12.string().min(1),
3691
+ object: z12.string().min(1),
3692
+ source: z12.string().min(1).describe("Provenance URI (required \u2014 writes without it are rejected)")
3693
+ });
3694
+ function graphAddTool(store) {
3695
+ return defineTool({
3696
+ name: "graph_add",
3697
+ description: "Write facts to the knowledge graph. Validated (SHACL/provenance/functional); violations return repair feedback.",
3698
+ schema: z12.object({ quads: z12.array(TurtleQuadSchema).min(1).max(50) }),
3699
+ execute: async ({ quads }) => {
3700
+ try {
3701
+ await store.add(quads);
3702
+ const inferred = (await store.match(null, null, null)).filter((q) => q.source === "entailment");
3703
+ return { committed: quads.length, inferred: inferred.length };
3704
+ } catch (err) {
3705
+ return `Validation failed \u2014 fix and retry: ${err instanceof Error ? err.message : String(err)}`;
3706
+ }
3707
+ },
3708
+ toResult: (output) => ({
3709
+ content: typeof output === "string" ? output : JSON.stringify(output),
3710
+ artifact: typeof output === "string" ? void 0 : { committed: output.committed }
3711
+ })
3712
+ });
3713
+ }
3714
+ function claimCheckTool(store) {
3715
+ return defineTool({
3716
+ name: "claim_check",
3717
+ description: "Verify a fact against the graph before asserting it. Returns grounded | unknown | contradicted with proof.",
3718
+ schema: z12.object({ subject: z12.string().min(1), predicate: z12.string().min(1), object: z12.string().min(1) }),
3719
+ execute: async ({ subject, predicate, object }) => {
3720
+ const direct = await store.match(subject, predicate, object);
3721
+ if (direct.length) {
3722
+ return { verdict: "grounded", proof: await store.why(direct[0]) };
3723
+ }
3724
+ const siblings = await store.match(subject, predicate, null);
3725
+ const declarations = await store.match(predicate, null, null);
3726
+ const functional = declarations.some((q) => RDF_TYPE2.has(q.predicate) && OWL_FUNCTIONAL.has(q.object));
3727
+ const clash = siblings.filter((q) => q.object !== object);
3728
+ if (functional && clash.length) {
3729
+ return {
3730
+ verdict: "contradicted",
3731
+ proof: { existing: clash.map((q) => q.object) }
3732
+ };
3733
+ }
3734
+ return {
3735
+ verdict: "unknown",
3736
+ proof: siblings.length ? { related: siblings.map((q) => q.object) } : null
3737
+ };
3738
+ }
3739
+ });
3740
+ }
3741
+ var GraphMemory = class {
3742
+ constructor(store, inner, opts = {}) {
3743
+ this.store = store;
3744
+ this.inner = inner;
3745
+ this.opts = opts;
3746
+ }
3747
+ store;
3748
+ inner;
3749
+ opts;
3750
+ async load() {
3751
+ const messages = await this.inner.load();
3752
+ const lastUser = [...messages].reverse().find((m) => m.role === "user");
3753
+ if (!lastUser) return messages;
3754
+ const terms = new Set(tokenize(typeof lastUser.content === "string" ? lastUser.content : ""));
3755
+ if (!terms.size) return messages;
3756
+ const quads = await this.store.match(null, null, null);
3757
+ const scored = quads.map((q) => {
3758
+ const hay = `${q.subject} ${q.predicate} ${q.object}`.toLowerCase();
3759
+ let score = 0;
3760
+ for (const t of terms) if (hay.includes(t)) score += 1;
3761
+ return { q, score };
3762
+ }).filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, this.opts.facts ?? 8);
3763
+ if (!scored.length) return messages;
3764
+ const lines = scored.map((s) => `- ${s.q.subject} ${s.q.predicate} ${s.q.object}`);
3765
+ return [...messages, { role: "assistant", content: `Relevant known facts (ground answers in these):
3766
+ ${lines.join("\n")}` }];
3767
+ }
3768
+ async save(messages) {
3769
+ await this.inner.save(messages);
3770
+ }
3771
+ async clear() {
3772
+ await this.inner.clear();
3773
+ }
3774
+ /** Serialize the closure for snapshots / long-term persistence. */
3775
+ async exportState() {
3776
+ return { quads: await this.store.match(null, null, null) };
3777
+ }
3778
+ /** Restore a previously exported closure (facts become explicit again). */
3779
+ async importState(state) {
3780
+ const fresh = state.quads.filter((q) => q.source !== "entailment");
3781
+ if (fresh.length) await this.store.add(fresh);
3782
+ }
3783
+ };
3784
+ export {
3785
+ A2AClient,
3786
+ A2AServer,
3787
+ Agent,
3788
+ AgentClient,
3789
+ AgentError,
3790
+ AgentState,
3791
+ AnthropicProvider,
3792
+ AudioBlockSchema,
3793
+ ChatMessageSchema,
3794
+ ClearToolUsesEdit,
3795
+ CompactedMemory,
3796
+ ConsoleSpanSink,
3797
+ ContentBlockSchema,
3798
+ EntityLinker,
3799
+ FileBlockSchema,
3800
+ FileCheckpointer,
3801
+ FileMemory,
3802
+ FrameworkFileSchema,
3803
+ GraphMemory,
3804
+ HitlInterrupt,
3805
+ ImageBlockSchema,
3806
+ InMemoryGraphStore,
3807
+ InMemoryMemory,
3808
+ InMemoryRetriever,
3809
+ InMemoryStorage,
3810
+ JSONLD_PREFIXES,
3811
+ JsonlSpanSink,
3812
+ LABEL_PREDICATES,
3813
+ LocalFileStorage,
3814
+ MappingProposals,
3815
+ McpHttpClient,
3816
+ McpStdioClient,
3817
+ MemorySpanSink,
3818
+ MockModelProvider,
3819
+ NonStandardBlockSchema,
3820
+ NullConversationManager,
3821
+ OWL_VOCAB,
3822
+ OllamaProvider,
3823
+ OpenAICompatibleProvider,
3824
+ ReasoningBlockSchema,
3825
+ RemoteSparqlAdapter,
3826
+ SessionManager,
3827
+ SkillRegistry,
3828
+ SlidingWindowConversationManager,
3829
+ SummarizingConversationManager,
3830
+ Swarm,
3831
+ TBOX_PREDICATES,
3832
+ TextBlockSchema,
3833
+ ToolCallBlockSchema,
3834
+ ToolRegistry,
3835
+ ToolRejection,
3836
+ TurtleQuadSchema,
3837
+ __resetConfig,
3838
+ a2aAsTool,
3839
+ abPrompts,
3840
+ adoptShapes,
3841
+ assertCanHandle,
3842
+ assistant,
3843
+ backoffDelayMs,
3844
+ buildServedAgent,
3845
+ claimCheckTool,
3846
+ collectStream,
3847
+ connectMcpHttp,
3848
+ connectMcpStdio,
3849
+ consoleEvents,
3850
+ contextEditingMiddleware,
3851
+ createMiddleware,
3852
+ createSandboxBackend,
3853
+ createSubAgentMiddleware,
3854
+ defaultSummarize,
3855
+ defaultToResult,
3856
+ definePrompt,
3857
+ defineTool,
3858
+ deserializeMessages,
3859
+ discoverSkills,
3860
+ estimateCostUsd,
3861
+ estimateTokens,
3862
+ executeSelect,
3863
+ filesystemMiddleware,
3864
+ filesystemTools,
3865
+ getRetryable,
3866
+ graphAddTool,
3867
+ handleAgentRoute,
3868
+ hasImages,
3869
+ historyTokens,
3870
+ hitlKey,
3871
+ humanInTheLoopMiddleware,
3872
+ imagesOf,
3873
+ initChatModel,
3874
+ instantiate,
3875
+ jsonSchemaToZod,
3876
+ jsonldToQuads,
3877
+ linkEntityTool,
3878
+ llmToolSelectorMiddleware,
3879
+ loadConfig,
3880
+ loadEnvFile,
3881
+ loadFrameworkConfig,
3882
+ loadJsonl,
3883
+ loadPromptFile,
3884
+ loadSkillsInto,
3885
+ logError,
3886
+ matchGlob,
3887
+ matchPatterns,
3888
+ matchSkills,
3889
+ mcpToolsToFramework,
3890
+ messageTokens,
3891
+ migrateTBox,
3892
+ modelCallLimit,
3893
+ modelFallbackMiddleware,
3894
+ modelRetryMiddleware,
3895
+ ollama,
3896
+ ontologyLookupTool,
3897
+ parseSelect,
3898
+ parseSkillFile,
3899
+ parseStructured,
3900
+ piiMiddleware,
3901
+ policyFilterMiddleware,
3902
+ priceFor,
3903
+ printReport,
3904
+ proposeMappingTool,
3905
+ proposeShapes,
3906
+ proposeShapesTool,
3907
+ providerFromEnv,
3908
+ providerStrategy,
3909
+ quadsToJsonLd,
3910
+ readHandoffSignal,
3911
+ remoteSparqlTool,
3912
+ responseFormatJsonSchema,
3913
+ responseFormatName,
3914
+ retrievalTool,
3915
+ reviewOntology,
3916
+ reviewOntologyTool,
3917
+ runConformance,
3918
+ runEval,
3919
+ runJavascriptTool,
3920
+ runOrchestrated,
3921
+ runWithBudget,
3922
+ runWorkflow,
3923
+ scoreSkills,
3924
+ selectSnapshotFields,
3925
+ serializeMessages,
3926
+ serveFramework,
3927
+ sleep,
3928
+ sparqlRetriever,
3929
+ sparqlSelectTool,
3930
+ stampRetryable,
3931
+ summarizationMiddleware,
3932
+ supportsColor,
3933
+ supportsVision,
3934
+ system,
3935
+ textOf,
3936
+ todoListMiddleware,
3937
+ tokenize,
3938
+ toolCallLimitMiddleware,
3939
+ toolEmulatorMiddleware,
3940
+ toolResult,
3941
+ toolRetry,
3942
+ toolRetryMiddleware,
3943
+ toolStrategy,
3944
+ tracingEvents,
3945
+ trimDangling,
3946
+ usageCost,
3947
+ user,
3948
+ wireTextOf,
3949
+ withFileLock
3950
+ };
3951
+ //# sourceMappingURL=index.js.map