Viking Updates
This commit is contained in:
1 parent
8c623caa3b
commit
133c0cab47
5 files changed
+234
No files matched your search
@@ -0,0 +1,10 @@
|
||||
# Memory-First Policy (OpenViking)
|
||||
|
||||
Use this policy text in any agent `systemPrompt` where you want lower token usage:
|
||||
|
||||
1. For non-trivial tasks, call `openviking_search` first with `top_k` 3-5.
|
||||
2. If relevant matches exist, answer from retrieved evidence instead of broad context expansion.
|
||||
3. If needed, call `openviking_read` only on the top hits.
|
||||
4. Keep injected context compact (summary + small excerpts), avoid large raw dumps.
|
||||
5. After producing reusable output, call `openviking_upsert` with concise notes.
|
||||
6. If OpenViking is unavailable, continue using normal model fallbacks without blocking.
|
||||
@@ -6,10 +6,12 @@ This folder is safe to commit to GitHub. It contains config and installers, but
|
||||
|
||||
- `templates/opencode.json`
|
||||
- `templates/oh-my-opencode.json`
|
||||
- `templates/scripts/openviking-mcp.js`
|
||||
- `scripts/install-linux.sh`
|
||||
- `scripts/install-windows.ps1`
|
||||
- `.env.example`
|
||||
- `OBSIDIAN_KNOWLEDGE_WORKFLOW.md`
|
||||
- `MEMORY_FIRST_POLICY.md`
|
||||
|
||||
## How to use
|
||||
|
||||
@@ -36,6 +38,17 @@ $env:INSTALL_IMPECCABLE="1"
|
||||
.\scripts\install-windows.ps1
|
||||
```
|
||||
|
||||
## OpenViking MCP bridge
|
||||
|
||||
The bundle includes a local OpenViking bridge script (`scripts/openviking-mcp.js`).
|
||||
|
||||
- Default OpenViking server URL: `http://127.0.0.1:1933`
|
||||
- Override with env var: `OPENVIKING_BASE_URL`
|
||||
- Tools exposed to OpenCode:
|
||||
- `openviking_search`
|
||||
- `openviking_read`
|
||||
- `openviking_upsert`
|
||||
|
||||
## Important
|
||||
|
||||
- Do not commit `.env`.
|
||||
|
||||
@@ -14,6 +14,9 @@ echo "Installing OpenCode config to: $TARGET_DIR"
|
||||
mkdir -p "$TARGET_DIR"
|
||||
cp "$TEMPLATES_DIR/opencode.json" "$TARGET_DIR/opencode.json"
|
||||
cp "$TEMPLATES_DIR/oh-my-opencode.json" "$TARGET_DIR/oh-my-opencode.json"
|
||||
mkdir -p "$TARGET_DIR/scripts"
|
||||
cp "$TEMPLATES_DIR/scripts/openviking-mcp.js" "$TARGET_DIR/scripts/openviking-mcp.js"
|
||||
chmod +x "$TARGET_DIR/scripts/openviking-mcp.js"
|
||||
|
||||
echo "Installing optional components (Node/Bun packages)..."
|
||||
if command -v bun >/dev/null 2>&1; then
|
||||
|
||||
@@ -13,6 +13,8 @@ Write-Host "Installing OpenCode config to: $TargetDir"
|
||||
New-Item -ItemType Directory -Path $TargetDir -Force | Out-Null
|
||||
Copy-Item (Join-Path $TemplatesDir "opencode.json") (Join-Path $TargetDir "opencode.json") -Force
|
||||
Copy-Item (Join-Path $TemplatesDir "oh-my-opencode.json") (Join-Path $TargetDir "oh-my-opencode.json") -Force
|
||||
New-Item -ItemType Directory -Path (Join-Path $TargetDir "scripts") -Force | Out-Null
|
||||
Copy-Item (Join-Path $TemplatesDir "scripts\openviking-mcp.js") (Join-Path $TargetDir "scripts\openviking-mcp.js") -Force
|
||||
|
||||
Write-Host "Installing optional components (Node/Bun packages)..."
|
||||
if (Get-Command bun -ErrorAction SilentlyContinue) {
|
||||
|
||||
Executable
+206
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const BASE_URL = (process.env.OPENVIKING_BASE_URL || "http://127.0.0.1:1933").replace(/\/+$/, "");
|
||||
const SEARCH_PATHS = (process.env.OPENVIKING_SEARCH_PATHS || "/api/v1/search,/find")
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
const READ_PATHS = (process.env.OPENVIKING_READ_PATHS || "/api/v1/read,/read")
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
const UPSERT_PATHS = (process.env.OPENVIKING_UPSERT_PATHS || "/api/v1/add_resource,/add_resource")
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
function writeMessage(msg) {
|
||||
const body = Buffer.from(JSON.stringify(msg), "utf8");
|
||||
process.stdout.write(`Content-Length: ${body.length}\r\n\r\n`);
|
||||
process.stdout.write(body);
|
||||
}
|
||||
|
||||
async function postWithFallback(paths, payload) {
|
||||
let lastError = null;
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
lastError = new Error(`HTTP ${res.status} on ${path}: ${text.slice(0, 400)}`);
|
||||
continue;
|
||||
}
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
}
|
||||
}
|
||||
throw lastError || new Error("All endpoint attempts failed");
|
||||
}
|
||||
|
||||
function makeTools() {
|
||||
return [
|
||||
{
|
||||
name: "openviking_search",
|
||||
description: "Search OpenViking memory/context and return top matches.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string", description: "Search query." },
|
||||
top_k: { type: "integer", minimum: 1, maximum: 20, default: 5 },
|
||||
namespace: { type: "string", description: "Optional namespace/project bucket." },
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "openviking_read",
|
||||
description: "Read a resource from OpenViking by URI or ID.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
uri: { type: "string" },
|
||||
id: { type: "string" },
|
||||
namespace: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "openviking_upsert",
|
||||
description: "Add or update a memory resource in OpenViking.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
uri: { type: "string" },
|
||||
title: { type: "string" },
|
||||
content: { type: "string" },
|
||||
namespace: { type: "string" },
|
||||
metadata: { type: "object" },
|
||||
},
|
||||
required: ["content"],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async function handleToolCall(name, args) {
|
||||
if (name === "openviking_search") {
|
||||
const payload = {
|
||||
query: args.query,
|
||||
top_k: args.top_k ?? 5,
|
||||
namespace: args.namespace,
|
||||
};
|
||||
const data = await postWithFallback(SEARCH_PATHS, payload);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
||||
};
|
||||
}
|
||||
if (name === "openviking_read") {
|
||||
const payload = {
|
||||
uri: args.uri,
|
||||
id: args.id,
|
||||
namespace: args.namespace,
|
||||
};
|
||||
const data = await postWithFallback(READ_PATHS, payload);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
||||
};
|
||||
}
|
||||
if (name === "openviking_upsert") {
|
||||
const payload = {
|
||||
id: args.id,
|
||||
uri: args.uri,
|
||||
title: args.title,
|
||||
content: args.content,
|
||||
namespace: args.namespace,
|
||||
metadata: args.metadata,
|
||||
};
|
||||
const data = await postWithFallback(UPSERT_PATHS, payload);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
||||
};
|
||||
}
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
|
||||
async function onRequest(msg) {
|
||||
const { id, method, params } = msg;
|
||||
try {
|
||||
if (method === "initialize") {
|
||||
writeMessage({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
result: {
|
||||
protocolVersion: "2024-11-05",
|
||||
serverInfo: { name: "openviking-mcp-bridge", version: "0.1.0" },
|
||||
capabilities: { tools: {} },
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (method === "notifications/initialized") {
|
||||
return;
|
||||
}
|
||||
if (method === "tools/list") {
|
||||
writeMessage({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
result: { tools: makeTools() },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (method === "tools/call") {
|
||||
const toolName = params?.name;
|
||||
const args = params?.arguments || {};
|
||||
const result = await handleToolCall(toolName, args);
|
||||
writeMessage({ jsonrpc: "2.0", id, result });
|
||||
return;
|
||||
}
|
||||
writeMessage({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: { code: -32601, message: `Method not found: ${method}` },
|
||||
});
|
||||
} catch (err) {
|
||||
writeMessage({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: { code: -32000, message: err && err.message ? err.message : String(err) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let buffer = Buffer.alloc(0);
|
||||
process.stdin.on("data", (chunk) => {
|
||||
buffer = Buffer.concat([buffer, chunk]);
|
||||
while (true) {
|
||||
const headerEnd = buffer.indexOf("\r\n\r\n");
|
||||
if (headerEnd === -1) break;
|
||||
const headerText = buffer.slice(0, headerEnd).toString("utf8");
|
||||
const match = headerText.match(/Content-Length:\s*(\d+)/i);
|
||||
if (!match) {
|
||||
buffer = buffer.slice(headerEnd + 4);
|
||||
continue;
|
||||
}
|
||||
const length = Number(match[1]);
|
||||
const total = headerEnd + 4 + length;
|
||||
if (buffer.length < total) break;
|
||||
const body = buffer.slice(headerEnd + 4, total).toString("utf8");
|
||||
buffer = buffer.slice(total);
|
||||
let msg;
|
||||
try {
|
||||
msg = JSON.parse(body);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (msg && msg.jsonrpc === "2.0" && msg.method) {
|
||||
onRequest(msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user