@drewsepsi/nextpi 0.1.2 → 0.1.3

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/lib/.gitkeep ADDED
File without changes
package/lib/agent.ts ADDED
@@ -0,0 +1,105 @@
1
+ import {
2
+ createAgentSession,
3
+ SessionManager,
4
+ type AgentSession
5
+ } from "@mariozechner/pi-coding-agent";
6
+ import { getModel, streamSimple } from "@mariozechner/pi-ai";
7
+ import * as path from "path";
8
+ import * as fs from "fs";
9
+ import { getProjectRoot } from "./root-path";
10
+ import { getEffectiveApiKey } from "./config";
11
+
12
+ export interface ResearchAgentConfig {
13
+ sessionDir?: string;
14
+ sessionFile?: string;
15
+ modelId?: string;
16
+ thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
17
+ }
18
+
19
+ // Default OpenRouter model
20
+ const DEFAULT_MODEL = "openrouter/free";
21
+
22
+ // Research tools (web search, web fetch)
23
+ const researchTools = [
24
+ {
25
+ name: "web_search",
26
+ description: "Search the web for information using Exa AI",
27
+ parameters: {
28
+ type: "object",
29
+ properties: {
30
+ query: { type: "string", description: "Search query" },
31
+ numResults: { type: "number", description: "Number of results (default: 5)" }
32
+ },
33
+ required: ["query"]
34
+ }
35
+ },
36
+ {
37
+ name: "web_fetch",
38
+ description: "Fetch content from a specific URL",
39
+ parameters: {
40
+ type: "object",
41
+ properties: {
42
+ url: { type: "string", description: "URL to fetch" }
43
+ },
44
+ required: ["url"]
45
+ }
46
+ }
47
+ ];
48
+
49
+ export async function createResearchAgent(
50
+ config: ResearchAgentConfig = {}
51
+ ): Promise<AgentSession> {
52
+ const {
53
+ sessionDir = path.join(getProjectRoot(), ".sessions"),
54
+ sessionFile = path.join(sessionDir, "research-agent.jsonl"),
55
+ modelId = DEFAULT_MODEL,
56
+ thinkingLevel = "low",
57
+ } = config;
58
+
59
+ // Ensure session directory exists
60
+ fs.mkdirSync(sessionDir, { recursive: true });
61
+
62
+ // Use OpenRouter model
63
+ const model = getModel("openrouter", modelId as "openrouter/free");
64
+
65
+ // Create session with research tools and system prompt
66
+ const { session } = await createAgentSession({
67
+ model,
68
+ thinkingLevel,
69
+ systemPrompt: RESEARCH_SYSTEM_PROMPT,
70
+ sessionManager: SessionManager.open(sessionFile),
71
+ customTools: researchTools as any,
72
+ } as any);
73
+
74
+ // Wrap stream function with OpenRouter headers
75
+ const originalStreamFn = session.agent.streamFn;
76
+ session.agent.streamFn = (model, context, options) => {
77
+ return streamSimple(model, context, {
78
+ ...options,
79
+ headers: {
80
+ ...options?.headers,
81
+ "X-Title": "NextPi Agent",
82
+ "HTTP-Referer": "https://github.com/drewsepsi/nextpi",
83
+ },
84
+ });
85
+ };
86
+
87
+ return session;
88
+ }
89
+
90
+ // System prompt for research and coding
91
+ export const RESEARCH_SYSTEM_PROMPT = `You are NextPi, an expert research and coding assistant. You have access to the web and the local file system.
92
+
93
+ ### GUIDELINES
94
+ - ALWAYS start by explaining your plan to the user in the main message or thinking block.
95
+ - BEFORE calling any tool that modifies the file system (write_file, edit_file, etc.), you MUST describe the specific changes you are about to make and why.
96
+ - If you are editing a file, summarize the intended changes first.
97
+ - If you are researching, explain what you hope to find.
98
+
99
+ ### CAPABILITIES
100
+ 1. **Web Search**: Search for docs, errors, news, etc.
101
+ 2. **Web Fetch**: Read specific URLs.
102
+ 3. **File Operations**: Read, write, list (ls), and edit files.
103
+ 4. **Shell**: Execute bash commands for testing or exploration.
104
+
105
+ Always be surgical with edits and transparent with your process.`;
package/lib/config.js ADDED
@@ -0,0 +1,45 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+
5
+ const CONFIG_DIR = path.join(os.homedir(), '.nextpi');
6
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
7
+
8
+ export function getConfig() {
9
+ try {
10
+ if (!fs.existsSync(CONFIG_FILE)) {
11
+ return {};
12
+ }
13
+ const data = fs.readFileSync(CONFIG_FILE, 'utf-8');
14
+ return JSON.parse(data);
15
+ } catch (err) {
16
+ console.error('Error reading config:', err);
17
+ return {};
18
+ }
19
+ }
20
+
21
+ export function saveConfig(newConfig) {
22
+ try {
23
+ if (!fs.existsSync(CONFIG_DIR)) {
24
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
25
+ }
26
+ const current = getConfig();
27
+ const updated = { ...current, ...newConfig };
28
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2));
29
+
30
+ // Also update current process env for immediate use
31
+ if (updated.openRouterApiKey) {
32
+ process.env.OPENROUTER_API_KEY = updated.openRouterApiKey;
33
+ }
34
+
35
+ return updated;
36
+ } catch (err) {
37
+ console.error('Error saving config:', err);
38
+ throw err;
39
+ }
40
+ }
41
+
42
+ export function getEffectiveApiKey() {
43
+ // Priority: 1. Environment Variable, 2. Config File
44
+ return process.env.OPENROUTER_API_KEY || getConfig().openRouterApiKey;
45
+ }
package/lib/config.ts ADDED
@@ -0,0 +1,50 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+
5
+ const CONFIG_DIR = path.join(os.homedir(), '.nextpi');
6
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
7
+
8
+ export interface NextPiConfig {
9
+ openRouterApiKey?: string;
10
+ defaultModel?: string;
11
+ }
12
+
13
+ export function getConfig(): NextPiConfig {
14
+ try {
15
+ if (!fs.existsSync(CONFIG_FILE)) {
16
+ return {};
17
+ }
18
+ const data = fs.readFileSync(CONFIG_FILE, 'utf-8');
19
+ return JSON.parse(data);
20
+ } catch (err) {
21
+ console.error('Error reading config:', err);
22
+ return {};
23
+ }
24
+ }
25
+
26
+ export function saveConfig(newConfig: Partial<NextPiConfig>) {
27
+ try {
28
+ if (!fs.existsSync(CONFIG_DIR)) {
29
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
30
+ }
31
+ const current = getConfig();
32
+ const updated = { ...current, ...newConfig };
33
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2));
34
+
35
+ // Also update current process env for immediate use
36
+ if (updated.openRouterApiKey) {
37
+ process.env.OPENROUTER_API_KEY = updated.openRouterApiKey;
38
+ }
39
+
40
+ return updated;
41
+ } catch (err) {
42
+ console.error('Error saving config:', err);
43
+ throw err;
44
+ }
45
+ }
46
+
47
+ export function getEffectiveApiKey(): string | undefined {
48
+ // Priority: 1. Environment Variable, 2. Config File
49
+ return process.env.OPENROUTER_API_KEY || getConfig().openRouterApiKey;
50
+ }
@@ -0,0 +1,15 @@
1
+ import path from 'path';
2
+
3
+ export function getProjectRoot() {
4
+ // PI_ROOT can be set via environment variable to override the root directory
5
+ const envRoot = process.env.PI_ROOT;
6
+
7
+ if (envRoot) {
8
+ // Resolve the path to handle relative paths from where the process was started
9
+ return path.resolve(envRoot);
10
+ }
11
+
12
+ // If no PI_ROOT is set, fallback to process.cwd()
13
+ // Note: in a Next.js environment during dev/build, process.cwd() is the project root.
14
+ return process.cwd();
15
+ }
@@ -0,0 +1,48 @@
1
+ import { createResearchAgent } from '@/lib/agent';
2
+
3
+ type AgentSession = Awaited<ReturnType<typeof createResearchAgent>>;
4
+
5
+ /**
6
+ * In Next.js development mode, modules are frequently re-evaluated due to HMR.
7
+ * Attaching the session to `globalThis` ensures that all API routes (which may
8
+ * be running in different contexts or re-evaluated modules) share exactly
9
+ * the same AgentSession instance.
10
+ */
11
+ const globalForAgent = globalThis as unknown as {
12
+ _sharedSession?: AgentSession;
13
+ _sharedSessionPromise?: Promise<AgentSession>;
14
+ };
15
+
16
+ let currentModelId: string | undefined = undefined;
17
+
18
+ export async function getSharedSession(): Promise<AgentSession> {
19
+ // Return the existing session if already initialized
20
+ if (globalForAgent._sharedSession) {
21
+ return globalForAgent._sharedSession;
22
+ }
23
+
24
+ // If a session is being created, wait for it
25
+ if (globalForAgent._sharedSessionPromise) {
26
+ return globalForAgent._sharedSessionPromise;
27
+ }
28
+
29
+ // Otherwise, create a new session and store the promise globally
30
+ console.log(`[Agent] Initializing new shared session singleton (Model: ${currentModelId || 'default'})...`);
31
+ globalForAgent._sharedSessionPromise = createResearchAgent({ modelId: currentModelId }).then((session) => {
32
+ globalForAgent._sharedSession = session;
33
+ console.log('[Agent] Shared session initialized');
34
+ return session;
35
+ });
36
+
37
+ return globalForAgent._sharedSessionPromise;
38
+ }
39
+
40
+ export function resetSharedSession(modelId?: string): void {
41
+ console.log(`[Agent] Resetting shared session${modelId ? ` to model ${modelId}` : ''}...`);
42
+ if (modelId) {
43
+ currentModelId = modelId;
44
+ }
45
+ globalForAgent._sharedSession = undefined;
46
+ globalForAgent._sharedSessionPromise = undefined;
47
+ }
48
+
package/lib/utils.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drewsepsi/nextpi",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "The Agentic IDE - An AI-powered research and coding assistant workspace.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "files": [
10
10
  "bin",
11
+ "lib",
11
12
  ".next/standalone",
12
13
  ".next/static",
13
14
  "public",