@arnilo/prism 0.3.1 → 0.4.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/CHANGELOG.md +58 -0
- package/README.md +34 -57
- package/dist/agent-definitions.js +4 -1
- package/dist/agent-run-lifecycle.js +4 -0
- package/dist/agent-run-state.d.ts +4 -0
- package/dist/agent-run-state.js +18 -5
- package/dist/agent-session/session.d.ts +7 -0
- package/dist/agent-session/session.js +59 -2
- package/dist/cli-dev.d.ts +29 -0
- package/dist/cli-dev.js +52 -0
- package/dist/cli-init.d.ts +17 -2
- package/dist/cli-init.js +194 -21
- package/dist/cli-runner.d.ts +5 -1
- package/dist/cli-runner.js +12 -1
- package/dist/contracts-core/agent.d.ts +29 -2
- package/dist/contracts-protocol.d.ts +18 -0
- package/dist/contracts-run-state.d.ts +1 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/input.d.ts +8 -0
- package/dist/input.js +4 -0
- package/dist/rpc.d.ts +4 -1
- package/dist/rpc.js +5 -1
- package/dist/testing/persistence-schema.d.ts +1 -1
- package/dist/testing/persistence-schema.js +32 -28
- package/dist/testing/tool-conformance.d.ts +25 -0
- package/dist/testing/tool-conformance.js +128 -1
- package/dist/tool-search.d.ts +76 -0
- package/dist/tool-search.js +199 -0
- package/docs/0.1.0-readiness.md +2 -2
- package/docs/acp-agent.md +1 -1
- package/docs/agent-definitions.md +1 -1
- package/docs/antigravity-agent.md +1 -1
- package/docs/browser-automation.md +5 -5
- package/docs/caveman.md +2 -2
- package/docs/cli-rpc.md +26 -3
- package/docs/coding-agent-tools.md +7 -1
- package/docs/coding-security.md +1 -1
- package/docs/coding-tools.md +82 -0
- package/docs/compaction-and-retry.md +5 -2
- package/docs/compaction-llm.md +4 -4
- package/docs/compaction-observational-memory.md +49 -2
- package/docs/context-and-skills.md +2 -0
- package/docs/core.md +85 -0
- package/docs/credential-storage.md +1 -1
- package/docs/database-persistence.md +4 -0
- package/docs/dev-inspector.md +103 -0
- package/docs/diagrams.md +247 -0
- package/docs/documents.md +213 -0
- package/docs/evaluations.md +35 -1
- package/docs/extension-authoring.md +42 -0
- package/docs/graft.md +3 -3
- package/docs/guardrails.md +1 -1
- package/docs/host-security.md +4 -3
- package/docs/impeccable.md +2 -2
- package/docs/index.md +34 -23
- package/docs/mcp-tools.md +1 -1
- package/docs/migrate-to-0.4.md +312 -0
- package/docs/migration.md +22 -0
- package/docs/model-routing.md +1 -1
- package/docs/multi-agent-patterns.md +177 -0
- package/docs/multimodal-content.md +1 -1
- package/docs/obscura.md +10 -10
- package/docs/openapi-tools.md +1 -1
- package/docs/performance.md +23 -3
- package/docs/persistence-credentials-multimodality-primitives.md +1 -1
- package/docs/policy-and-audit.md +1 -1
- package/docs/ponytail.md +2 -2
- package/docs/prompt-registry.md +106 -0
- package/docs/provider-caching.md +32 -32
- package/docs/provider-conformance.md +1 -1
- package/docs/provider-packages.md +19 -19
- package/docs/provider-primitives.md +4 -4
- package/docs/providers/ai-sdk.md +3 -3
- package/docs/providers/alibaba.md +5 -5
- package/docs/providers/anthropic.md +6 -6
- package/docs/providers/azure.md +3 -3
- package/docs/providers/bedrock.md +3 -3
- package/docs/providers/clinepass.md +3 -3
- package/docs/providers/deepseek.md +3 -3
- package/docs/providers/google.md +4 -4
- package/docs/providers/kimi.md +3 -3
- package/docs/providers/neuralwatt.md +8 -8
- package/docs/providers/ollama.md +3 -3
- package/docs/providers/openai-compatible.md +1 -1
- package/docs/providers/openai.md +5 -5
- package/docs/providers/opencode-go.md +4 -4
- package/docs/providers/openrouter.md +3 -3
- package/docs/providers/vertex.md +5 -5
- package/docs/providers/xai.md +3 -3
- package/docs/providers/zai.md +3 -3
- package/docs/public-contracts.md +1 -1
- package/docs/rag.md +5 -5
- package/docs/release-and-install.md +116 -50
- package/docs/runs-and-usage.md +14 -1
- package/docs/server.md +90 -1
- package/docs/sheets.md +229 -0
- package/docs/supervisors.md +9 -1
- package/docs/thinking-and-reasoning.md +10 -10
- package/docs/tool-conformance.md +27 -2
- package/docs/tools.md +29 -2
- package/docs/web-tools.md +2 -2
- package/docs/wiki.md +24 -10
- package/docs/workflow-orchestration-primitives.md +24 -0
- package/docs/workflows.md +102 -8
- package/docs/working-and-semantic-memory.md +53 -5
- package/package.json +10 -30
- package/templates/README.md +23 -0
- package/templates/deep-research/README.md.tmpl +47 -0
- package/templates/deep-research/env.example.tmpl +12 -0
- package/templates/deep-research/gitignore.tmpl +7 -0
- package/templates/deep-research/manifest.json +12 -0
- package/templates/deep-research/package.json.tmpl +23 -0
- package/templates/deep-research/src/agent.ts.tmpl +81 -0
- package/templates/deep-research/src/index.ts.tmpl +53 -0
- package/templates/deep-research/src/tests/research.test.ts.tmpl +114 -0
- package/templates/deep-research/src/tools.ts.tmpl +86 -0
- package/templates/deep-research/src/types.ts.tmpl +45 -0
- package/templates/deep-research/src/workflow.ts.tmpl +156 -0
- package/templates/deep-research/tsconfig.json.tmpl +15 -0
- package/templates/init/manifest.json +5 -0
- package/templates/init/package.json.tmpl +2 -1
- package/templates/init/providers.json +16 -16
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createAppAgent, createPlannerAgent, createSynthesizerAgent } from "./agent.js";
|
|
2
|
+
import { createClarifyTool, createResearchSearchAdapter, createResearchWebTools } from "./tools.js";
|
|
3
|
+
import type { ResearchFinding, ResearchPlan, ResearchReport } from "./types.js";
|
|
4
|
+
import { createResearchWorkflow, executeResearch } from "./workflow.js";
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
createAppAgent,
|
|
8
|
+
createPlannerAgent,
|
|
9
|
+
createSynthesizerAgent,
|
|
10
|
+
createResearchSearchAdapter,
|
|
11
|
+
createResearchWebTools,
|
|
12
|
+
createClarifyTool,
|
|
13
|
+
createResearchWorkflow,
|
|
14
|
+
executeResearch,
|
|
15
|
+
type ResearchPlan,
|
|
16
|
+
type ResearchFinding,
|
|
17
|
+
type ResearchReport,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export async function main() {
|
|
21
|
+
const topic = process.argv.slice(2).join(" ") || "Prism Agent Architecture";
|
|
22
|
+
console.log(`Starting deep research on: "${topic}"\n`);
|
|
23
|
+
|
|
24
|
+
const report = await executeResearch(topic, {
|
|
25
|
+
onEvent: (event) => {
|
|
26
|
+
if (event.type === "node_started") {
|
|
27
|
+
console.log(` -> [${event.nodeId}] started...`);
|
|
28
|
+
} else if (event.type === "node_finished") {
|
|
29
|
+
console.log(` ✓ [${event.nodeId}] finished`);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
console.log("\n========================================================");
|
|
35
|
+
console.log(`RESEARCH REPORT: ${report.topic}`);
|
|
36
|
+
console.log("========================================================");
|
|
37
|
+
console.log(`\n${report.summary}\n`);
|
|
38
|
+
console.log("Key Findings:");
|
|
39
|
+
for (const f of report.findings) {
|
|
40
|
+
console.log(`- [${f.citationId}] ${f.title}`);
|
|
41
|
+
console.log(` ${f.snippet}`);
|
|
42
|
+
console.log(` Source: ${f.url}\n`);
|
|
43
|
+
}
|
|
44
|
+
console.log("Citations & References:");
|
|
45
|
+
for (const c of report.citations) {
|
|
46
|
+
console.log(` [${c.citationId}] ${c.title} — ${c.url}`);
|
|
47
|
+
}
|
|
48
|
+
console.log("========================================================\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
52
|
+
await main();
|
|
53
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { citation, createWebTools } from "@arnilo/prism-web-tools";
|
|
4
|
+
import { createAppAgent, createPlannerAgent } from "../agent.js";
|
|
5
|
+
import { createClarifyTool, createResearchSearchAdapter, formatResearchFindings } from "../tools.js";
|
|
6
|
+
import { createResearchWorkflow, executeResearch } from "../workflow.js";
|
|
7
|
+
|
|
8
|
+
describe("deep-research template", () => {
|
|
9
|
+
it("generates research plans with structured query breakdowns", async () => {
|
|
10
|
+
const planner = createPlannerAgent();
|
|
11
|
+
const session = planner.createSession({ id: "test-plan" });
|
|
12
|
+
const result = await session.run("Prism architecture");
|
|
13
|
+
|
|
14
|
+
assert.equal(result.status, "succeeded");
|
|
15
|
+
assert.ok(result.text.length > 0);
|
|
16
|
+
const plan = JSON.parse(result.text);
|
|
17
|
+
assert.ok(Array.isArray(plan.queries));
|
|
18
|
+
assert.ok(plan.queries.length >= 2);
|
|
19
|
+
assert.ok(plan.queries[0].query);
|
|
20
|
+
assert.ok(plan.queries[0].aspect);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("produces attributable citations for search findings", async () => {
|
|
24
|
+
const customUrl = "https://docs.prism.ai/workflows";
|
|
25
|
+
const customCitation = citation("brave", customUrl);
|
|
26
|
+
|
|
27
|
+
const adapter = createResearchSearchAdapter({
|
|
28
|
+
searchFn: async (query) => [
|
|
29
|
+
{
|
|
30
|
+
...customCitation,
|
|
31
|
+
title: `Custom result for ${query}`,
|
|
32
|
+
snippet: "Deterministic snippet for verification",
|
|
33
|
+
retrievedAt: "2026-08-31T00:00:00.000Z",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const response = await adapter.search("workflows", { count: 1 });
|
|
39
|
+
assert.equal(response.results.length, 1);
|
|
40
|
+
const finding = formatResearchFindings("workflows", response.results)[0]!;
|
|
41
|
+
|
|
42
|
+
assert.equal(finding.url, customUrl);
|
|
43
|
+
assert.equal(finding.citationId, customCitation.citationId);
|
|
44
|
+
assert.ok(finding.citationId.startsWith("web:brave:"));
|
|
45
|
+
assert.equal(finding.title, "Custom result for workflows");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("clarify tool executes human-in-the-loop decision resolution", async () => {
|
|
49
|
+
let receivedQuestion = "";
|
|
50
|
+
let receivedChoices: readonly string[] = [];
|
|
51
|
+
|
|
52
|
+
const tool = createClarifyTool({
|
|
53
|
+
onClarify: async (question, choices) => {
|
|
54
|
+
receivedQuestion = question;
|
|
55
|
+
receivedChoices = choices;
|
|
56
|
+
return choices[1]!; // select second choice
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const context = {
|
|
61
|
+
toolCallId: "call-clarify-1",
|
|
62
|
+
sessionId: "session-1",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const result = await tool.execute(
|
|
66
|
+
{
|
|
67
|
+
question: "Which aspect of Prism should we focus on?",
|
|
68
|
+
choices: ["Orchestration DAGs", "Web Tools & Search", "RAG & Citations"],
|
|
69
|
+
},
|
|
70
|
+
context,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
assert.equal(result.name, "clarify_research_scope");
|
|
74
|
+
assert.equal(receivedQuestion, "Which aspect of Prism should we focus on?");
|
|
75
|
+
assert.equal(receivedChoices.length, 3);
|
|
76
|
+
const val = result.value as { question: string; selectedOption: string };
|
|
77
|
+
assert.equal(val.selectedOption, "Web Tools & Search");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("executes the full research workflow DAG offline within bounded limits", async () => {
|
|
81
|
+
const events: string[] = [];
|
|
82
|
+
const report = await executeResearch("Autonomous Agents", {
|
|
83
|
+
maxIterations: 2,
|
|
84
|
+
onEvent: (e) => events.push(e.type),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
assert.ok(report);
|
|
88
|
+
assert.equal(report.topic, "Autonomous Agents");
|
|
89
|
+
assert.ok(report.findings.length >= 2);
|
|
90
|
+
assert.ok(report.citations.length >= 1);
|
|
91
|
+
assert.ok(report.summary.includes("Autonomous Agents"));
|
|
92
|
+
|
|
93
|
+
// Verify all findings carry valid, attributable citation IDs matching citations list
|
|
94
|
+
const citationIds = new Set(report.citations.map((c) => c.citationId));
|
|
95
|
+
for (const finding of report.findings) {
|
|
96
|
+
assert.ok(finding.citationId, "Finding must have a citation ID");
|
|
97
|
+
assert.ok(citationIds.has(finding.citationId), `Finding citationId ${finding.citationId} must be in report citations`);
|
|
98
|
+
assert.ok(finding.url.startsWith("https://"), "Finding must have a valid URL");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Verify workflow lifecycle events
|
|
102
|
+
assert.ok(events.includes("node_started"));
|
|
103
|
+
assert.ok(events.includes("node_finished"));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("default createAppAgent boots and exposes search tools", async () => {
|
|
107
|
+
const appAgent = createAppAgent();
|
|
108
|
+
const session = appAgent.createSession({ id: "app-test" });
|
|
109
|
+
const result = await session.run("Hello");
|
|
110
|
+
|
|
111
|
+
assert.equal(result.status, "succeeded");
|
|
112
|
+
assert.match(result.text, /Deep Research Agent ready/);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type ToolDefinition, type ToolExecutionContext, type ToolResult } from "@arnilo/prism";
|
|
2
|
+
import { citation, createWebTools, type WebSearchAdapter, type WebSearchResult } from "@arnilo/prism-web-tools";
|
|
3
|
+
import type { ResearchFinding } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates search tools backed by web-tools with attributable citation IDs.
|
|
7
|
+
*/
|
|
8
|
+
export function createResearchSearchAdapter(options?: {
|
|
9
|
+
readonly searchFn?: (query: string) => Promise<readonly WebSearchResult[]>;
|
|
10
|
+
}): WebSearchAdapter {
|
|
11
|
+
return {
|
|
12
|
+
provider: "brave",
|
|
13
|
+
search: async (query: string) => {
|
|
14
|
+
if (options?.searchFn) {
|
|
15
|
+
const results = await options.searchFn(query);
|
|
16
|
+
return { provider: "brave", query, results, untrusted: true };
|
|
17
|
+
}
|
|
18
|
+
// Offline fallback mock results with attributable citations
|
|
19
|
+
const url = `https://docs.prism.ai/research/${encodeURIComponent(query.toLowerCase().replace(/\s+/g, "-"))}`;
|
|
20
|
+
const baseCitation = citation("brave", url);
|
|
21
|
+
const mockResult: WebSearchResult = {
|
|
22
|
+
...baseCitation,
|
|
23
|
+
title: `Research findings for ${query}`,
|
|
24
|
+
snippet: `Verified architectural overview and technical analysis regarding ${query}.`,
|
|
25
|
+
retrievedAt: new Date().toISOString(),
|
|
26
|
+
};
|
|
27
|
+
return { provider: "brave", query, results: [mockResult], untrusted: true };
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create web tools for the research agent.
|
|
35
|
+
*/
|
|
36
|
+
export function createResearchWebTools(adapter: WebSearchAdapter): readonly ToolDefinition[] {
|
|
37
|
+
return createWebTools({ search: adapter });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Normalizes raw search results into domain ResearchFindings with citations.
|
|
42
|
+
*/
|
|
43
|
+
export function formatResearchFindings(query: string, results: readonly WebSearchResult[]): readonly ResearchFinding[] {
|
|
44
|
+
return results.map((r) => ({
|
|
45
|
+
query,
|
|
46
|
+
title: r.title ?? "Untitled Resource",
|
|
47
|
+
snippet: r.snippet ?? "",
|
|
48
|
+
url: r.url,
|
|
49
|
+
citationId: r.citationId,
|
|
50
|
+
retrievedAt: r.retrievedAt ?? new Date().toISOString(),
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Simple Human-in-the-Loop clarify tool for resolving broad research topics.
|
|
56
|
+
*/
|
|
57
|
+
export function createClarifyTool(options: {
|
|
58
|
+
readonly onClarify?: (question: string, choices: readonly string[]) => Promise<string>;
|
|
59
|
+
}): ToolDefinition {
|
|
60
|
+
return {
|
|
61
|
+
name: "clarify_research_scope",
|
|
62
|
+
description: "Propose clarification questions to the user when a research topic is broad or ambiguous.",
|
|
63
|
+
parameters: {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: {
|
|
66
|
+
question: { type: "string" },
|
|
67
|
+
choices: { type: "array", items: { type: "string" }, minItems: 2 },
|
|
68
|
+
},
|
|
69
|
+
required: ["question", "choices"],
|
|
70
|
+
additionalProperties: false,
|
|
71
|
+
},
|
|
72
|
+
execute: async (args: Record<string, unknown>, context: ToolExecutionContext): Promise<ToolResult> => {
|
|
73
|
+
const question = String(args.question ?? "");
|
|
74
|
+
const choices = Array.isArray(args.choices) ? (args.choices as string[]) : [];
|
|
75
|
+
let selected = choices[0] ?? "default";
|
|
76
|
+
if (options.onClarify) {
|
|
77
|
+
selected = await options.onClarify(question, choices);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
toolCallId: context.toolCallId,
|
|
81
|
+
name: "clarify_research_scope",
|
|
82
|
+
value: { question, selectedOption: selected },
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep Research domain types and contracts.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ResearchQuery {
|
|
6
|
+
readonly query: string;
|
|
7
|
+
readonly rationale: string;
|
|
8
|
+
readonly aspect: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ResearchPlan {
|
|
12
|
+
readonly topic: string;
|
|
13
|
+
readonly queries: readonly ResearchQuery[];
|
|
14
|
+
readonly clarifyQuestion?: string;
|
|
15
|
+
readonly clarifyOptions?: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ResearchFinding {
|
|
19
|
+
readonly query: string;
|
|
20
|
+
readonly title: string;
|
|
21
|
+
readonly snippet: string;
|
|
22
|
+
readonly url: string;
|
|
23
|
+
readonly citationId: string;
|
|
24
|
+
readonly retrievedAt: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ResearchCitation {
|
|
28
|
+
readonly citationId: string;
|
|
29
|
+
readonly url: string;
|
|
30
|
+
readonly title: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ResearchReport {
|
|
34
|
+
readonly topic: string;
|
|
35
|
+
readonly summary: string;
|
|
36
|
+
readonly findings: readonly ResearchFinding[];
|
|
37
|
+
readonly citations: readonly ResearchCitation[];
|
|
38
|
+
readonly iterations: number;
|
|
39
|
+
readonly completedAt: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ClarifyDecision {
|
|
43
|
+
readonly question: string;
|
|
44
|
+
readonly selectedOption: string;
|
|
45
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createSecretRedactor } from "@arnilo/prism";
|
|
2
|
+
import {
|
|
3
|
+
createMemoryWorkflowCheckpoints,
|
|
4
|
+
defineWorkflow,
|
|
5
|
+
functionNode,
|
|
6
|
+
runWorkflow,
|
|
7
|
+
type WorkflowDefinition,
|
|
8
|
+
type WorkflowEvent,
|
|
9
|
+
} from "@arnilo/prism-workflows";
|
|
10
|
+
import { createPlannerAgent, createSynthesizerAgent } from "./agent.js";
|
|
11
|
+
import { createResearchSearchAdapter, formatResearchFindings } from "./tools.js";
|
|
12
|
+
import type { ResearchCitation, ResearchFinding, ResearchPlan, ResearchReport } from "./types.js";
|
|
13
|
+
|
|
14
|
+
export interface ResearchWorkflowOptions {
|
|
15
|
+
readonly maxIterations?: number;
|
|
16
|
+
readonly searchAdapter?: import("@arnilo/prism-web-tools").WebSearchAdapter;
|
|
17
|
+
readonly onEvent?: (event: WorkflowEvent) => void;
|
|
18
|
+
readonly onClarify?: (question: string, choices: readonly string[]) => Promise<string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Builds the deep research workflow DAG.
|
|
23
|
+
*/
|
|
24
|
+
export function createResearchWorkflow(options?: ResearchWorkflowOptions): WorkflowDefinition {
|
|
25
|
+
const maxIterations = options?.maxIterations ?? 2;
|
|
26
|
+
const searchAdapter = options?.searchAdapter ?? createResearchSearchAdapter();
|
|
27
|
+
|
|
28
|
+
const plan = functionNode({
|
|
29
|
+
execute: async (ctx) => {
|
|
30
|
+
const input = (ctx.workflowInput as { topic: string }) ?? { topic: "General Research" };
|
|
31
|
+
// Planning step: create initial structured plan
|
|
32
|
+
const planResult: ResearchPlan = {
|
|
33
|
+
topic: input.topic,
|
|
34
|
+
queries: [
|
|
35
|
+
{ query: `${input.topic} overview and core architecture`, rationale: "Foundational concepts", aspect: "architecture" },
|
|
36
|
+
{ query: `${input.topic} performance and security considerations`, rationale: "Production considerations", aspect: "security" },
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
return planResult;
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const search = functionNode({
|
|
44
|
+
execute: async (ctx) => {
|
|
45
|
+
const currentPlan = ctx.upstream.plan as ResearchPlan;
|
|
46
|
+
const findings: ResearchFinding[] = [];
|
|
47
|
+
|
|
48
|
+
for (const query of currentPlan.queries) {
|
|
49
|
+
const response = await searchAdapter.search(query.query, { count: 2, signal: ctx.signal });
|
|
50
|
+
const normalized = formatResearchFindings(query.query, response.results);
|
|
51
|
+
findings.push(...normalized);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { findings };
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const refine = functionNode({
|
|
59
|
+
execute: async (ctx) => {
|
|
60
|
+
const searchOutput = ctx.upstream.search as { findings: readonly ResearchFinding[] };
|
|
61
|
+
const currentFindings = [...searchOutput.findings];
|
|
62
|
+
let iterations = 1;
|
|
63
|
+
|
|
64
|
+
// Bounded refine loop: verify aspect coverage and execute additional queries if bounded budget allows
|
|
65
|
+
if (iterations < maxIterations && currentFindings.length < 4) {
|
|
66
|
+
iterations += 1;
|
|
67
|
+
const refineQuery = `${(ctx.upstream.plan as ResearchPlan).topic} best practices`;
|
|
68
|
+
const extra = await searchAdapter.search(refineQuery, { count: 1, signal: ctx.signal });
|
|
69
|
+
currentFindings.push(...formatResearchFindings(refineQuery, extra.results));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { findings: currentFindings, iterations };
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const synthesize = functionNode({
|
|
77
|
+
execute: async (ctx) => {
|
|
78
|
+
const planOutput = ctx.upstream.plan as ResearchPlan;
|
|
79
|
+
const refineOutput = ctx.upstream.refine as { findings: readonly ResearchFinding[]; iterations: number };
|
|
80
|
+
const findings = refineOutput.findings;
|
|
81
|
+
|
|
82
|
+
// Collect attributable citations
|
|
83
|
+
const citationsMap = new Map<string, ResearchCitation>();
|
|
84
|
+
for (const f of findings) {
|
|
85
|
+
if (!citationsMap.has(f.citationId)) {
|
|
86
|
+
citationsMap.set(f.citationId, {
|
|
87
|
+
citationId: f.citationId,
|
|
88
|
+
url: f.url,
|
|
89
|
+
title: f.title,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const citations = Array.from(citationsMap.values());
|
|
95
|
+
const summary = `Research completed on topic "${planOutput.topic}". Synthesized ${findings.length} findings across ${citations.length} verified sources.`;
|
|
96
|
+
|
|
97
|
+
const report: ResearchReport = {
|
|
98
|
+
topic: planOutput.topic,
|
|
99
|
+
summary,
|
|
100
|
+
findings,
|
|
101
|
+
citations,
|
|
102
|
+
iterations: refineOutput.iterations,
|
|
103
|
+
completedAt: new Date().toISOString(),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return report;
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return defineWorkflow({
|
|
111
|
+
id: "deep-research-workflow",
|
|
112
|
+
revision: "1",
|
|
113
|
+
nodes: { plan, search, refine, synthesize },
|
|
114
|
+
edges: [
|
|
115
|
+
["plan", "search"],
|
|
116
|
+
["search", "refine"],
|
|
117
|
+
["refine", "synthesize"],
|
|
118
|
+
],
|
|
119
|
+
limits: {
|
|
120
|
+
maxConcurrency: 2,
|
|
121
|
+
maxFanOut: 4,
|
|
122
|
+
maxNodes: 16,
|
|
123
|
+
maxStateBytes: 65_536,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Execute the deep research workflow on a specified topic.
|
|
130
|
+
*/
|
|
131
|
+
export async function executeResearch(
|
|
132
|
+
topic: string,
|
|
133
|
+
options?: ResearchWorkflowOptions,
|
|
134
|
+
): Promise<ResearchReport> {
|
|
135
|
+
const workflow = createResearchWorkflow(options);
|
|
136
|
+
const redactor = createSecretRedactor([]);
|
|
137
|
+
const checkpoints = createMemoryWorkflowCheckpoints({ redactor });
|
|
138
|
+
|
|
139
|
+
const result = await runWorkflow(
|
|
140
|
+
workflow,
|
|
141
|
+
{ topic },
|
|
142
|
+
{
|
|
143
|
+
checkpoints,
|
|
144
|
+
redactor,
|
|
145
|
+
ownership: { tenantId: "research-session" },
|
|
146
|
+
signal: AbortSignal.timeout(60_000),
|
|
147
|
+
onEvent: options?.onEvent,
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
if (result.status !== "completed") {
|
|
152
|
+
throw new Error(`Research workflow did not complete successfully. Status: ${result.status}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return result.outputs.synthesize as ResearchReport;
|
|
156
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noImplicitOverride": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"]
|
|
15
|
+
}
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
"build": "tsc -p tsconfig.json",
|
|
8
8
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
9
9
|
"test": "npm run build && node --test dist/__tests__/agent.test.js",
|
|
10
|
-
"start": "npm run build && node dist/index.js"
|
|
10
|
+
"start": "npm run build && node dist/index.js",
|
|
11
|
+
"dev": "prism dev"
|
|
11
12
|
},
|
|
12
13
|
"dependencies": {
|
|
13
14
|
__DEPENDENCIES__
|
|
@@ -9,89 +9,89 @@
|
|
|
9
9
|
},
|
|
10
10
|
"openai": {
|
|
11
11
|
"id": "openai",
|
|
12
|
-
"packageName": "@arnilo/prism-
|
|
12
|
+
"packageName": "@arnilo/prism-providers",
|
|
13
13
|
"envKey": "OPENAI_API_KEY",
|
|
14
14
|
"envPlaceholder": "sk-...",
|
|
15
15
|
"modelProvider": "openai",
|
|
16
16
|
"modelName": "gpt-5.1",
|
|
17
|
-
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport { createOpenAIResponsesProvider } from \"@arnilo/prism-
|
|
17
|
+
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport { createOpenAIResponsesProvider } from \"@arnilo/prism-providers/openai\";",
|
|
18
18
|
"providerExpression": "createOpenAIResponsesProvider({\n apiKey: () => process.env.OPENAI_API_KEY,\n })",
|
|
19
19
|
"modelExpression": "{ provider: \"openai\", model: \"gpt-5.1\" }"
|
|
20
20
|
},
|
|
21
21
|
"openrouter": {
|
|
22
22
|
"id": "openrouter",
|
|
23
|
-
"packageName": "@arnilo/prism-
|
|
23
|
+
"packageName": "@arnilo/prism-providers",
|
|
24
24
|
"envKey": "OPENROUTER_API_KEY",
|
|
25
25
|
"envPlaceholder": "sk-or-...",
|
|
26
26
|
"modelProvider": "openrouter",
|
|
27
27
|
"modelName": "anthropic/claude-sonnet-4",
|
|
28
|
-
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport {\n createOpenRouterProvider,\n defineOpenRouterModel,\n} from \"@arnilo/prism-
|
|
28
|
+
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport {\n createOpenRouterProvider,\n defineOpenRouterModel,\n} from \"@arnilo/prism-providers/openrouter\";",
|
|
29
29
|
"providerExpression": "createOpenRouterProvider({\n apiKey: () => process.env.OPENROUTER_API_KEY,\n })",
|
|
30
30
|
"modelExpression": "defineOpenRouterModel({ model: \"anthropic/claude-sonnet-4\" })"
|
|
31
31
|
},
|
|
32
32
|
"kimi": {
|
|
33
33
|
"id": "kimi",
|
|
34
|
-
"packageName": "@arnilo/prism-
|
|
34
|
+
"packageName": "@arnilo/prism-providers",
|
|
35
35
|
"envKey": "KIMI_API_KEY",
|
|
36
36
|
"envPlaceholder": "your-kimi-api-key",
|
|
37
37
|
"modelProvider": "kimi-coding",
|
|
38
38
|
"modelName": "kimi-k2.7-code",
|
|
39
|
-
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport { createKimiCodingProvider } from \"@arnilo/prism-
|
|
39
|
+
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport { createKimiCodingProvider } from \"@arnilo/prism-providers/kimi\";",
|
|
40
40
|
"providerExpression": "createKimiCodingProvider({\n apiKey: () => process.env.KIMI_API_KEY,\n })",
|
|
41
41
|
"modelExpression": "{ provider: \"kimi-coding\", model: \"kimi-k2.7-code\" }"
|
|
42
42
|
},
|
|
43
43
|
"zai": {
|
|
44
44
|
"id": "zai",
|
|
45
|
-
"packageName": "@arnilo/prism-
|
|
45
|
+
"packageName": "@arnilo/prism-providers",
|
|
46
46
|
"envKey": "ZAI_API_KEY",
|
|
47
47
|
"envPlaceholder": "your-zai-api-key",
|
|
48
48
|
"modelProvider": "zai",
|
|
49
49
|
"modelName": "glm-4.7",
|
|
50
|
-
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport { createZaiProvider } from \"@arnilo/prism-
|
|
50
|
+
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport { createZaiProvider } from \"@arnilo/prism-providers/zai\";",
|
|
51
51
|
"providerExpression": "createZaiProvider({\n apiKey: () => process.env.ZAI_API_KEY,\n })",
|
|
52
52
|
"modelExpression": "{ provider: \"zai\", model: \"glm-4.7\" }"
|
|
53
53
|
},
|
|
54
54
|
"opencode-go": {
|
|
55
55
|
"id": "opencode-go",
|
|
56
|
-
"packageName": "@arnilo/prism-
|
|
56
|
+
"packageName": "@arnilo/prism-providers",
|
|
57
57
|
"envKey": "OPENCODE_API_KEY",
|
|
58
58
|
"envPlaceholder": "your-opencode-api-key",
|
|
59
59
|
"modelProvider": "opencode-go",
|
|
60
60
|
"modelName": "gpt-5.1-go",
|
|
61
|
-
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport { createOpenCodeGoProvider } from \"@arnilo/prism-
|
|
61
|
+
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport { createOpenCodeGoProvider } from \"@arnilo/prism-providers/opencode-go\";",
|
|
62
62
|
"providerExpression": "createOpenCodeGoProvider({\n apiKey: () => process.env.OPENCODE_API_KEY,\n })",
|
|
63
63
|
"modelExpression": "{ provider: \"opencode-go\", model: \"gpt-5.1-go\", compat: { route: \"openai\" } }"
|
|
64
64
|
},
|
|
65
65
|
"neuralwatt": {
|
|
66
66
|
"id": "neuralwatt",
|
|
67
|
-
"packageName": "@arnilo/prism-
|
|
67
|
+
"packageName": "@arnilo/prism-providers",
|
|
68
68
|
"envKey": "NEURALWATT_API_KEY",
|
|
69
69
|
"envPlaceholder": "your-neuralwatt-api-key",
|
|
70
70
|
"modelProvider": "neuralwatt",
|
|
71
71
|
"modelName": "glm-5.2",
|
|
72
|
-
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport {\n createNeuralWattProvider,\n defineNeuralWattModel,\n} from \"@arnilo/prism-
|
|
72
|
+
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport {\n createNeuralWattProvider,\n defineNeuralWattModel,\n} from \"@arnilo/prism-providers/neuralwatt\";",
|
|
73
73
|
"providerExpression": "createNeuralWattProvider({\n apiKey: () => process.env.NEURALWATT_API_KEY,\n })",
|
|
74
74
|
"modelExpression": "defineNeuralWattModel({\n model: \"glm-5.2\",\n cache: { kind: \"implicit\" },\n })"
|
|
75
75
|
},
|
|
76
76
|
"alibaba": {
|
|
77
77
|
"id": "alibaba",
|
|
78
|
-
"packageName": "@arnilo/prism-
|
|
78
|
+
"packageName": "@arnilo/prism-providers",
|
|
79
79
|
"envKey": "DASHSCOPE_API_KEY",
|
|
80
80
|
"envPlaceholder": "sk-...",
|
|
81
81
|
"modelProvider": "alibaba",
|
|
82
82
|
"modelName": "qwen-plus",
|
|
83
|
-
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport {\n createAlibabaProvider,\n defineAlibabaModel,\n} from \"@arnilo/prism-
|
|
83
|
+
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport {\n createAlibabaProvider,\n defineAlibabaModel,\n} from \"@arnilo/prism-providers/alibaba\";",
|
|
84
84
|
"providerExpression": "createAlibabaProvider({\n apiKey: () => process.env.DASHSCOPE_API_KEY,\n })",
|
|
85
85
|
"modelExpression": "defineAlibabaModel({ model: \"qwen-plus\" })"
|
|
86
86
|
},
|
|
87
87
|
"ollama": {
|
|
88
88
|
"id": "ollama",
|
|
89
|
-
"packageName": "@arnilo/prism-
|
|
89
|
+
"packageName": "@arnilo/prism-providers",
|
|
90
90
|
"envKey": "OLLAMA_API_KEY",
|
|
91
91
|
"envPlaceholder": "your-ollama-api-key",
|
|
92
92
|
"modelProvider": "ollama",
|
|
93
93
|
"modelName": "gpt-oss:20b",
|
|
94
|
-
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport {\n createOllamaProvider,\n defineOllamaModel,\n} from \"@arnilo/prism-
|
|
94
|
+
"imports": "import { createAgent } from \"@arnilo/prism\";\nimport {\n createOllamaProvider,\n defineOllamaModel,\n} from \"@arnilo/prism-providers/ollama\";",
|
|
95
95
|
"providerExpression": "createOllamaProvider({\n apiKey: () => process.env.OLLAMA_API_KEY,\n })",
|
|
96
96
|
"modelExpression": "defineOllamaModel({ model: \"gpt-oss:20b\" })"
|
|
97
97
|
}
|