@openvole/paw-gemini 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/README.md +40 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +293 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
- package/vole-paw.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# @openvole/paw-gemini
|
|
2
|
+
|
|
3
|
+
**Brain Paw powered by Google Gemini.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@openvole/paw-gemini)
|
|
6
|
+
|
|
7
|
+
Part of [OpenVole](https://github.com/openvole/openvole) — the microkernel AI agent framework.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @openvole/paw-gemini
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Config
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"name": "@openvole/paw-gemini",
|
|
20
|
+
"allow": {
|
|
21
|
+
"network": ["generativelanguage.googleapis.com"],
|
|
22
|
+
"env": ["GEMINI_API_KEY", "GEMINI_MODEL"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Environment Variables
|
|
28
|
+
|
|
29
|
+
| Variable | Description |
|
|
30
|
+
|----------|-------------|
|
|
31
|
+
| `GEMINI_API_KEY` | Google Gemini API key |
|
|
32
|
+
| `GEMINI_MODEL` | Model to use (e.g. `gemini-pro`) |
|
|
33
|
+
|
|
34
|
+
## Brain
|
|
35
|
+
|
|
36
|
+
Implements the Think phase — receives the conversation context and registered tools, returns the next assistant message and any tool calls. Connects to the Google Gemini API.
|
|
37
|
+
|
|
38
|
+
## License
|
|
39
|
+
|
|
40
|
+
[MIT](https://github.com/openvole/pawhub/blob/main/LICENSE)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { definePaw } from "@openvole/paw-sdk";
|
|
3
|
+
|
|
4
|
+
// src/paw.ts
|
|
5
|
+
import * as fs from "fs/promises";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import {
|
|
8
|
+
GoogleGenerativeAI,
|
|
9
|
+
SchemaType
|
|
10
|
+
} from "@google/generative-ai";
|
|
11
|
+
var genAI;
|
|
12
|
+
var generativeModel;
|
|
13
|
+
var model = "gemini-2.0-flash";
|
|
14
|
+
var identityContext;
|
|
15
|
+
var customBrainPrompt;
|
|
16
|
+
async function loadBrainPrompt() {
|
|
17
|
+
try {
|
|
18
|
+
const content = await fs.readFile(
|
|
19
|
+
path.resolve(process.cwd(), ".openvole", "BRAIN.md"),
|
|
20
|
+
"utf-8"
|
|
21
|
+
);
|
|
22
|
+
if (content.trim()) {
|
|
23
|
+
console.log("[paw-gemini] loaded custom BRAIN.md prompt");
|
|
24
|
+
return content.trim();
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
async function loadIdentityFiles() {
|
|
31
|
+
const openvoleDir = path.resolve(process.cwd(), ".openvole");
|
|
32
|
+
const files = [
|
|
33
|
+
{ name: "SOUL.md", section: "Agent Identity" },
|
|
34
|
+
{ name: "USER.md", section: "User Profile" },
|
|
35
|
+
{ name: "AGENT.md", section: "Agent Rules" }
|
|
36
|
+
];
|
|
37
|
+
const parts = [];
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
try {
|
|
40
|
+
const content = await fs.readFile(path.join(openvoleDir, file.name), "utf-8");
|
|
41
|
+
if (content.trim()) {
|
|
42
|
+
parts.push(`## ${file.section}
|
|
43
|
+
${content.trim()}`);
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return parts.join("\n\n");
|
|
49
|
+
}
|
|
50
|
+
function getModel() {
|
|
51
|
+
if (!generativeModel) {
|
|
52
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
53
|
+
if (!apiKey) {
|
|
54
|
+
throw new Error("GEMINI_API_KEY environment variable is required");
|
|
55
|
+
}
|
|
56
|
+
model = process.env.GEMINI_MODEL || "gemini-2.0-flash";
|
|
57
|
+
genAI = new GoogleGenerativeAI(apiKey);
|
|
58
|
+
generativeModel = genAI.getGenerativeModel({ model });
|
|
59
|
+
}
|
|
60
|
+
return generativeModel;
|
|
61
|
+
}
|
|
62
|
+
function buildSystemPrompt(activeSkills, availableTools, metadata, identityCtx, customBrain) {
|
|
63
|
+
const now = /* @__PURE__ */ new Date();
|
|
64
|
+
const runtimeContext = `## Current Context
|
|
65
|
+
- Date: ${now.toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" })}
|
|
66
|
+
- Time: ${now.toLocaleTimeString("en-US", { hour12: true })}
|
|
67
|
+
- Platform: ${process.platform}
|
|
68
|
+
- Model: ${model}`;
|
|
69
|
+
const basePrompt = customBrain ?? `You are an AI agent powered by OpenVole. You accomplish tasks by using tools step by step.
|
|
70
|
+
|
|
71
|
+
## How to Work
|
|
72
|
+
1. Read the conversation history first \u2014 short user messages like an email or "yes" are answers to your previous questions
|
|
73
|
+
2. Break complex tasks into clear steps and execute them one at a time
|
|
74
|
+
3. After each tool call, examine the result carefully before deciding the next action
|
|
75
|
+
4. Never repeat the same tool call if it already succeeded \u2014 move to the next step
|
|
76
|
+
5. If a tool returns an error, try a different approach or different parameters
|
|
77
|
+
6. When you read important information (API docs, instructions, credentials), save it to workspace or memory immediately
|
|
78
|
+
7. When you have enough information to respond, do so directly \u2014 don't keep searching
|
|
79
|
+
8. If you cannot complete a task (missing credentials, access denied), explain exactly what you need and stop
|
|
80
|
+
|
|
81
|
+
## Data Management
|
|
82
|
+
- **Vault** (vault_store/get): ALL sensitive data \u2014 emails, passwords, API keys, tokens, credentials, usernames, handles, personal identifiers. ALWAYS use vault for these, NEVER memory or workspace.
|
|
83
|
+
- **Memory** (memory_write/read): General knowledge, non-sensitive facts, preferences, summaries
|
|
84
|
+
- **Workspace** (workspace_write/read): Files, documents, downloaded content, API docs, drafts
|
|
85
|
+
- **Session history**: Recent conversation \u2014 automatically available, review it before each response
|
|
86
|
+
|
|
87
|
+
## Recurring Tasks
|
|
88
|
+
When the user asks you to do something regularly, repeatedly, or on a schedule:
|
|
89
|
+
- **schedule_task**: Use this for tasks with a specific interval (e.g. "post every 6 hours", "check every 30 minutes"). Creates an automatic timer \u2014 no heartbeat needed.
|
|
90
|
+
- **heartbeat_write**: Use this ONLY for open-ended checks with no specific interval (e.g. "keep an eye on server status"). These run on the global heartbeat timer.
|
|
91
|
+
- Use ONE or the OTHER \u2014 never both for the same task. If you use schedule_task, do NOT also add it to HEARTBEAT.md.
|
|
92
|
+
- Do NOT just save recurring task requests to memory \u2014 that won't make them happen.
|
|
93
|
+
|
|
94
|
+
## Safety
|
|
95
|
+
- Never attempt to bypass access controls or escalate permissions
|
|
96
|
+
- Always ask for confirmation before performing destructive or irreversible actions
|
|
97
|
+
- Store credentials and personal identifiers ONLY in the vault \u2014 never in memory or workspace`;
|
|
98
|
+
const parts = [basePrompt, "", runtimeContext];
|
|
99
|
+
if (identityCtx) {
|
|
100
|
+
parts.push("");
|
|
101
|
+
parts.push(identityCtx);
|
|
102
|
+
}
|
|
103
|
+
if (metadata?.sessionHistory && typeof metadata.sessionHistory === "string") {
|
|
104
|
+
parts.push("");
|
|
105
|
+
parts.push("## Conversation History");
|
|
106
|
+
parts.push("Previous messages in this session. Use this to understand the current context and follow-up messages:");
|
|
107
|
+
parts.push(metadata.sessionHistory);
|
|
108
|
+
}
|
|
109
|
+
if (metadata?.memory && typeof metadata.memory === "string") {
|
|
110
|
+
parts.push("");
|
|
111
|
+
parts.push("## Agent Memory");
|
|
112
|
+
parts.push(metadata.memory);
|
|
113
|
+
}
|
|
114
|
+
if (activeSkills.length > 0) {
|
|
115
|
+
parts.push("");
|
|
116
|
+
parts.push("## Available Skills");
|
|
117
|
+
parts.push(
|
|
118
|
+
"The following skills are available. Use the skill_read tool to load full instructions when a skill is relevant to the current task."
|
|
119
|
+
);
|
|
120
|
+
for (const skill of activeSkills) {
|
|
121
|
+
parts.push(`- **${skill.name}**: ${skill.description}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (availableTools.length > 0) {
|
|
125
|
+
parts.push("");
|
|
126
|
+
parts.push("## Available Tools");
|
|
127
|
+
parts.push(
|
|
128
|
+
"You have access to the following tools. Use function calling to invoke them when needed."
|
|
129
|
+
);
|
|
130
|
+
for (const tool of availableTools) {
|
|
131
|
+
parts.push(`- **${tool.name}** (from ${tool.pawName}): ${tool.description}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return parts.join("\n");
|
|
135
|
+
}
|
|
136
|
+
function convertMessages(messages) {
|
|
137
|
+
const result = [];
|
|
138
|
+
for (const msg of messages) {
|
|
139
|
+
switch (msg.role) {
|
|
140
|
+
case "user":
|
|
141
|
+
result.push({
|
|
142
|
+
role: "user",
|
|
143
|
+
parts: [{ text: msg.content }]
|
|
144
|
+
});
|
|
145
|
+
break;
|
|
146
|
+
case "brain":
|
|
147
|
+
result.push({
|
|
148
|
+
role: "model",
|
|
149
|
+
parts: [{ text: msg.content }]
|
|
150
|
+
});
|
|
151
|
+
break;
|
|
152
|
+
case "tool_result":
|
|
153
|
+
result.push({
|
|
154
|
+
role: "function",
|
|
155
|
+
parts: [
|
|
156
|
+
{
|
|
157
|
+
functionResponse: {
|
|
158
|
+
name: msg.toolName || "unknown",
|
|
159
|
+
response: { result: msg.content }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
});
|
|
164
|
+
break;
|
|
165
|
+
case "error":
|
|
166
|
+
result.push({
|
|
167
|
+
role: "function",
|
|
168
|
+
parts: [
|
|
169
|
+
{
|
|
170
|
+
functionResponse: {
|
|
171
|
+
name: msg.toolName || "unknown",
|
|
172
|
+
response: { error: msg.content }
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
});
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
function convertTools(tools) {
|
|
183
|
+
if (tools.length === 0) return [];
|
|
184
|
+
const functionDeclarations = tools.map((tool) => ({
|
|
185
|
+
name: tool.name,
|
|
186
|
+
description: tool.description,
|
|
187
|
+
parameters: {
|
|
188
|
+
type: SchemaType.OBJECT,
|
|
189
|
+
properties: {}
|
|
190
|
+
}
|
|
191
|
+
}));
|
|
192
|
+
return [{ functionDeclarations }];
|
|
193
|
+
}
|
|
194
|
+
function parseToolCalls(parts) {
|
|
195
|
+
const actions = [];
|
|
196
|
+
for (const part of parts) {
|
|
197
|
+
if ("functionCall" in part && part.functionCall) {
|
|
198
|
+
actions.push({
|
|
199
|
+
tool: part.functionCall.name,
|
|
200
|
+
params: part.functionCall.args || {}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return actions;
|
|
205
|
+
}
|
|
206
|
+
var paw = {
|
|
207
|
+
name: "@openvole/paw-gemini",
|
|
208
|
+
version: "0.1.0",
|
|
209
|
+
description: "Brain Paw powered by Google Gemini",
|
|
210
|
+
brain: true,
|
|
211
|
+
async think(context) {
|
|
212
|
+
const geminiModel = getModel();
|
|
213
|
+
const start = Date.now();
|
|
214
|
+
try {
|
|
215
|
+
const systemPrompt = buildSystemPrompt(
|
|
216
|
+
context.activeSkills,
|
|
217
|
+
context.availableTools,
|
|
218
|
+
context.metadata,
|
|
219
|
+
identityContext,
|
|
220
|
+
customBrainPrompt
|
|
221
|
+
);
|
|
222
|
+
const geminiContents = convertMessages(context.messages);
|
|
223
|
+
const geminiTools = convertTools(context.availableTools);
|
|
224
|
+
const chat = geminiModel.startChat({
|
|
225
|
+
history: geminiContents.slice(0, -1),
|
|
226
|
+
systemInstruction: { role: "user", parts: [{ text: systemPrompt }] },
|
|
227
|
+
tools: geminiTools.length > 0 ? geminiTools : void 0
|
|
228
|
+
});
|
|
229
|
+
const lastContent = geminiContents[geminiContents.length - 1];
|
|
230
|
+
const lastParts = lastContent?.parts || [{ text: "" }];
|
|
231
|
+
const response = await chat.sendMessage(lastParts);
|
|
232
|
+
const durationMs = Date.now() - start;
|
|
233
|
+
console.log(
|
|
234
|
+
`[paw-gemini] think completed in ${durationMs}ms (model: ${model})`
|
|
235
|
+
);
|
|
236
|
+
const candidate = response.response.candidates?.[0];
|
|
237
|
+
if (!candidate) {
|
|
238
|
+
return {
|
|
239
|
+
actions: [],
|
|
240
|
+
response: "No response from Gemini.",
|
|
241
|
+
done: true
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const actions = parseToolCalls(candidate.content.parts);
|
|
245
|
+
if (actions.length > 0) {
|
|
246
|
+
return {
|
|
247
|
+
actions,
|
|
248
|
+
execution: "sequential"
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
const text = candidate.content.parts.filter((part) => "text" in part).map((part) => part.text).join("");
|
|
252
|
+
return {
|
|
253
|
+
actions: [],
|
|
254
|
+
response: text,
|
|
255
|
+
done: true
|
|
256
|
+
};
|
|
257
|
+
} catch (error) {
|
|
258
|
+
const durationMs = Date.now() - start;
|
|
259
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
260
|
+
console.error(
|
|
261
|
+
`[paw-gemini] think failed after ${durationMs}ms: ${message}`
|
|
262
|
+
);
|
|
263
|
+
return {
|
|
264
|
+
actions: [],
|
|
265
|
+
response: `Error communicating with Gemini API: ${message}`,
|
|
266
|
+
done: true
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
async onLoad() {
|
|
271
|
+
getModel();
|
|
272
|
+
customBrainPrompt = await loadBrainPrompt();
|
|
273
|
+
identityContext = await loadIdentityFiles();
|
|
274
|
+
if (identityContext) {
|
|
275
|
+
console.log("[paw-gemini] loaded identity files (SOUL.md, USER.md, AGENT.md)");
|
|
276
|
+
}
|
|
277
|
+
console.log(
|
|
278
|
+
`[paw-gemini] loaded \u2014 model: ${model}`
|
|
279
|
+
);
|
|
280
|
+
},
|
|
281
|
+
async onUnload() {
|
|
282
|
+
generativeModel = void 0;
|
|
283
|
+
genAI = void 0;
|
|
284
|
+
console.log("[paw-gemini] unloaded");
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// src/index.ts
|
|
289
|
+
var index_default = definePaw(paw);
|
|
290
|
+
export {
|
|
291
|
+
index_default as default
|
|
292
|
+
};
|
|
293
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/paw.ts"],"sourcesContent":["import { definePaw } from '@openvole/paw-sdk'\nimport { paw } from './paw.js'\n\nexport default definePaw(paw)\n","import * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport {\n\tGoogleGenerativeAI,\n\ttype GenerativeModel,\n\ttype Content,\n\ttype Part,\n\ttype FunctionDeclaration,\n\ttype Tool as GeminiTool,\n\tSchemaType,\n} from '@google/generative-ai'\nimport type {\n\tPawDefinition,\n\tAgentContext,\n\tAgentPlan,\n\tAgentMessage,\n\tActiveSkill,\n\tPlannedAction,\n\tToolSummary,\n} from '@openvole/paw-sdk'\n\nlet genAI: GoogleGenerativeAI | undefined\nlet generativeModel: GenerativeModel | undefined\nlet model: string = 'gemini-2.0-flash'\n\n/** Cached identity files — loaded once on startup */\nlet identityContext: string | undefined\n\n/** Custom brain prompt from BRAIN.md — overrides default system prompt if present */\nlet customBrainPrompt: string | undefined\n\n/** Load BRAIN.md from .openvole/ — if it exists, it replaces the default system prompt */\nasync function loadBrainPrompt(): Promise<string | undefined> {\n\ttry {\n\t\tconst content = await fs.readFile(\n\t\t\tpath.resolve(process.cwd(), '.openvole', 'BRAIN.md'),\n\t\t\t'utf-8',\n\t\t)\n\t\tif (content.trim()) {\n\t\t\tconsole.log('[paw-gemini] loaded custom BRAIN.md prompt')\n\t\t\treturn content.trim()\n\t\t}\n\t} catch {\n\t\t// No BRAIN.md — use default\n\t}\n\treturn undefined\n}\n\n/** Load identity files from .openvole/ (AGENT.md, USER.md, SOUL.md) */\nasync function loadIdentityFiles(): Promise<string> {\n\tconst openvoleDir = path.resolve(process.cwd(), '.openvole')\n\tconst files = [\n\t\t{ name: 'SOUL.md', section: 'Agent Identity' },\n\t\t{ name: 'USER.md', section: 'User Profile' },\n\t\t{ name: 'AGENT.md', section: 'Agent Rules' },\n\t]\n\n\tconst parts: string[] = []\n\tfor (const file of files) {\n\t\ttry {\n\t\t\tconst content = await fs.readFile(path.join(openvoleDir, file.name), 'utf-8')\n\t\t\tif (content.trim()) {\n\t\t\t\tparts.push(`## ${file.section}\\n${content.trim()}`)\n\t\t\t}\n\t\t} catch {\n\t\t\t// File doesn't exist — skip\n\t\t}\n\t}\n\n\treturn parts.join('\\n\\n')\n}\n\nfunction getModel(): GenerativeModel {\n\tif (!generativeModel) {\n\t\tconst apiKey = process.env.GEMINI_API_KEY\n\t\tif (!apiKey) {\n\t\t\tthrow new Error('GEMINI_API_KEY environment variable is required')\n\t\t}\n\t\tmodel = process.env.GEMINI_MODEL || 'gemini-2.0-flash'\n\t\tgenAI = new GoogleGenerativeAI(apiKey)\n\t\tgenerativeModel = genAI.getGenerativeModel({ model })\n\t}\n\treturn generativeModel\n}\n\nfunction buildSystemPrompt(\n\tactiveSkills: ActiveSkill[],\n\tavailableTools: ToolSummary[],\n\tmetadata?: Record<string, unknown>,\n\tidentityCtx?: string,\n\tcustomBrain?: string,\n): string {\n\tconst now = new Date()\n\tconst runtimeContext = `## Current Context\n- Date: ${now.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}\n- Time: ${now.toLocaleTimeString('en-US', { hour12: true })}\n- Platform: ${process.platform}\n- Model: ${model}`\n\n\t// Use custom BRAIN.md if provided, otherwise use default prompt\n\tconst basePrompt = customBrain ?? `You are an AI agent powered by OpenVole. You accomplish tasks by using tools step by step.\n\n## How to Work\n1. Read the conversation history first — short user messages like an email or \"yes\" are answers to your previous questions\n2. Break complex tasks into clear steps and execute them one at a time\n3. After each tool call, examine the result carefully before deciding the next action\n4. Never repeat the same tool call if it already succeeded — move to the next step\n5. If a tool returns an error, try a different approach or different parameters\n6. When you read important information (API docs, instructions, credentials), save it to workspace or memory immediately\n7. When you have enough information to respond, do so directly — don't keep searching\n8. If you cannot complete a task (missing credentials, access denied), explain exactly what you need and stop\n\n## Data Management\n- **Vault** (vault_store/get): ALL sensitive data — emails, passwords, API keys, tokens, credentials, usernames, handles, personal identifiers. ALWAYS use vault for these, NEVER memory or workspace.\n- **Memory** (memory_write/read): General knowledge, non-sensitive facts, preferences, summaries\n- **Workspace** (workspace_write/read): Files, documents, downloaded content, API docs, drafts\n- **Session history**: Recent conversation — automatically available, review it before each response\n\n## Recurring Tasks\nWhen the user asks you to do something regularly, repeatedly, or on a schedule:\n- **schedule_task**: Use this for tasks with a specific interval (e.g. \"post every 6 hours\", \"check every 30 minutes\"). Creates an automatic timer — no heartbeat needed.\n- **heartbeat_write**: Use this ONLY for open-ended checks with no specific interval (e.g. \"keep an eye on server status\"). These run on the global heartbeat timer.\n- Use ONE or the OTHER — never both for the same task. If you use schedule_task, do NOT also add it to HEARTBEAT.md.\n- Do NOT just save recurring task requests to memory — that won't make them happen.\n\n## Safety\n- Never attempt to bypass access controls or escalate permissions\n- Always ask for confirmation before performing destructive or irreversible actions\n- Store credentials and personal identifiers ONLY in the vault — never in memory or workspace`\n\n\tconst parts: string[] = [basePrompt, '', runtimeContext]\n\n\t// Inject identity context (SOUL.md, USER.md, AGENT.md) if available\n\tif (identityCtx) {\n\t\tparts.push('')\n\t\tparts.push(identityCtx)\n\t}\n\n\t// Inject session history if available — this is critical for conversation continuity\n\tif (metadata?.sessionHistory && typeof metadata.sessionHistory === 'string') {\n\t\tparts.push('')\n\t\tparts.push('## Conversation History')\n\t\tparts.push('Previous messages in this session. Use this to understand the current context and follow-up messages:')\n\t\tparts.push(metadata.sessionHistory)\n\t}\n\n\t// Inject memory if available\n\tif (metadata?.memory && typeof metadata.memory === 'string') {\n\t\tparts.push('')\n\t\tparts.push('## Agent Memory')\n\t\tparts.push(metadata.memory)\n\t}\n\n\tif (activeSkills.length > 0) {\n\t\tparts.push('')\n\t\tparts.push('## Available Skills')\n\t\tparts.push(\n\t\t\t'The following skills are available. Use the skill_read tool to load full instructions when a skill is relevant to the current task.',\n\t\t)\n\t\tfor (const skill of activeSkills) {\n\t\t\tparts.push(`- **${skill.name}**: ${skill.description}`)\n\t\t}\n\t}\n\n\tif (availableTools.length > 0) {\n\t\tparts.push('')\n\t\tparts.push('## Available Tools')\n\t\tparts.push(\n\t\t\t'You have access to the following tools. Use function calling to invoke them when needed.',\n\t\t)\n\t\tfor (const tool of availableTools) {\n\t\t\tparts.push(`- **${tool.name}** (from ${tool.pawName}): ${tool.description}`)\n\t\t}\n\t}\n\n\treturn parts.join('\\n')\n}\n\nfunction convertMessages(\n\tmessages: AgentMessage[],\n): Content[] {\n\tconst result: Content[] = []\n\n\tfor (const msg of messages) {\n\t\tswitch (msg.role) {\n\t\t\tcase 'user':\n\t\t\t\tresult.push({\n\t\t\t\t\trole: 'user',\n\t\t\t\t\tparts: [{ text: msg.content }],\n\t\t\t\t})\n\t\t\t\tbreak\n\t\t\tcase 'brain':\n\t\t\t\tresult.push({\n\t\t\t\t\trole: 'model',\n\t\t\t\t\tparts: [{ text: msg.content }],\n\t\t\t\t})\n\t\t\t\tbreak\n\t\t\tcase 'tool_result':\n\t\t\t\tresult.push({\n\t\t\t\t\trole: 'function',\n\t\t\t\t\tparts: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfunctionResponse: {\n\t\t\t\t\t\t\t\tname: (msg as any).toolName || 'unknown',\n\t\t\t\t\t\t\t\tresponse: { result: msg.content },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t})\n\t\t\t\tbreak\n\t\t\tcase 'error':\n\t\t\t\tresult.push({\n\t\t\t\t\trole: 'function',\n\t\t\t\t\tparts: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfunctionResponse: {\n\t\t\t\t\t\t\t\tname: (msg as any).toolName || 'unknown',\n\t\t\t\t\t\t\t\tresponse: { error: msg.content },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t})\n\t\t\t\tbreak\n\t\t}\n\t}\n\n\treturn result\n}\n\nfunction convertTools(\n\ttools: ToolSummary[],\n): GeminiTool[] {\n\tif (tools.length === 0) return []\n\n\tconst functionDeclarations: FunctionDeclaration[] = tools.map((tool) => ({\n\t\tname: tool.name,\n\t\tdescription: tool.description,\n\t\tparameters: {\n\t\t\ttype: SchemaType.OBJECT,\n\t\t\tproperties: {},\n\t\t},\n\t}))\n\n\treturn [{ functionDeclarations }]\n}\n\nfunction parseToolCalls(\n\tparts: Part[],\n): PlannedAction[] {\n\tconst actions: PlannedAction[] = []\n\n\tfor (const part of parts) {\n\t\tif ('functionCall' in part && part.functionCall) {\n\t\t\tactions.push({\n\t\t\t\ttool: part.functionCall.name,\n\t\t\t\tparams: (part.functionCall.args as Record<string, unknown>) || {},\n\t\t\t})\n\t\t}\n\t}\n\n\treturn actions\n}\n\nexport const paw: PawDefinition = {\n\tname: '@openvole/paw-gemini',\n\tversion: '0.1.0',\n\tdescription: 'Brain Paw powered by Google Gemini',\n\tbrain: true,\n\n\tasync think(context: AgentContext): Promise<AgentPlan> {\n\t\tconst geminiModel = getModel()\n\t\tconst start = Date.now()\n\n\t\ttry {\n\t\t\tconst systemPrompt = buildSystemPrompt(\n\t\t\t\tcontext.activeSkills,\n\t\t\t\tcontext.availableTools,\n\t\t\t\tcontext.metadata,\n\t\t\t\tidentityContext,\n\t\t\t\tcustomBrainPrompt,\n\t\t\t)\n\n\t\t\tconst geminiContents = convertMessages(context.messages)\n\t\t\tconst geminiTools = convertTools(context.availableTools)\n\n\t\t\tconst chat = geminiModel.startChat({\n\t\t\t\thistory: geminiContents.slice(0, -1),\n\t\t\t\tsystemInstruction: { role: 'user', parts: [{ text: systemPrompt }] },\n\t\t\t\ttools: geminiTools.length > 0 ? geminiTools : undefined,\n\t\t\t})\n\n\t\t\t// Send the last message (or a default if empty)\n\t\t\tconst lastContent = geminiContents[geminiContents.length - 1]\n\t\t\tconst lastParts = lastContent?.parts || [{ text: '' }]\n\n\t\t\tconst response = await chat.sendMessage(lastParts)\n\n\t\t\tconst durationMs = Date.now() - start\n\t\t\tconsole.log(\n\t\t\t\t`[paw-gemini] think completed in ${durationMs}ms (model: ${model})`,\n\t\t\t)\n\n\t\t\tconst candidate = response.response.candidates?.[0]\n\t\t\tif (!candidate) {\n\t\t\t\treturn {\n\t\t\t\t\tactions: [],\n\t\t\t\t\tresponse: 'No response from Gemini.',\n\t\t\t\t\tdone: true,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst actions = parseToolCalls(candidate.content.parts)\n\n\t\t\tif (actions.length > 0) {\n\t\t\t\treturn {\n\t\t\t\t\tactions,\n\t\t\t\t\texecution: 'sequential',\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst text = candidate.content.parts\n\t\t\t\t.filter((part): part is Part & { text: string } => 'text' in part)\n\t\t\t\t.map((part) => part.text)\n\t\t\t\t.join('')\n\n\t\t\treturn {\n\t\t\t\tactions: [],\n\t\t\t\tresponse: text,\n\t\t\t\tdone: true,\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst durationMs = Date.now() - start\n\t\t\tconst message =\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\tconsole.error(\n\t\t\t\t`[paw-gemini] think failed after ${durationMs}ms: ${message}`,\n\t\t\t)\n\n\t\t\treturn {\n\t\t\t\tactions: [],\n\t\t\t\tresponse: `Error communicating with Gemini API: ${message}`,\n\t\t\t\tdone: true,\n\t\t\t}\n\t\t}\n\t},\n\n\tasync onLoad() {\n\t\tgetModel()\n\t\tcustomBrainPrompt = await loadBrainPrompt()\n\t\tidentityContext = await loadIdentityFiles()\n\t\tif (identityContext) {\n\t\t\tconsole.log('[paw-gemini] loaded identity files (SOUL.md, USER.md, AGENT.md)')\n\t\t}\n\t\tconsole.log(\n\t\t\t`[paw-gemini] loaded — model: ${model}`,\n\t\t)\n\t},\n\n\tasync onUnload() {\n\t\tgenerativeModel = undefined\n\t\tgenAI = undefined\n\t\tconsole.log('[paw-gemini] unloaded')\n\t},\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openvole/paw-gemini",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Brain Paw powered by Google Gemini",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"typecheck": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@google/generative-ai": "^0.21.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.0.0",
|
|
23
|
+
"tsup": "^8.3.0",
|
|
24
|
+
"typescript": "^5.6.0",
|
|
25
|
+
"@openvole/paw-sdk": "^0.1.0"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20.0.0"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"vole-paw.json",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/openvole/pawhub",
|
|
39
|
+
"directory": "paws/paw-gemini"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"openvole",
|
|
43
|
+
"paw",
|
|
44
|
+
"gemini",
|
|
45
|
+
"google",
|
|
46
|
+
"brain",
|
|
47
|
+
"llm"
|
|
48
|
+
],
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@openvole/paw-sdk": "^0.1.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/vole-paw.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openvole/paw-gemini",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Brain Paw powered by Google Gemini",
|
|
5
|
+
"entry": "./dist/index.js",
|
|
6
|
+
"brain": true,
|
|
7
|
+
"inProcess": false,
|
|
8
|
+
"transport": "ipc",
|
|
9
|
+
"tools": [],
|
|
10
|
+
"permissions": {
|
|
11
|
+
"network": ["generativelanguage.googleapis.com"],
|
|
12
|
+
"listen": [],
|
|
13
|
+
"filesystem": [],
|
|
14
|
+
"env": ["GEMINI_API_KEY", "GEMINI_MODEL"]
|
|
15
|
+
}
|
|
16
|
+
}
|