mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
Fix Hermes adapter release package metadata
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
133c080b69
commit
fb11ed07fb
7 files changed
+329
-6
No files matched your search
@@ -31,6 +31,7 @@ COPY packages/adapters/cursor-cloud/package.json packages/adapters/cursor-cloud/
|
||||
COPY packages/adapters/cursor-local/package.json packages/adapters/cursor-local/
|
||||
COPY packages/adapters/gemini-local/package.json packages/adapters/gemini-local/
|
||||
COPY packages/adapters/grok-local/package.json packages/adapters/grok-local/
|
||||
COPY packages/adapters/hermes-local/package.json packages/adapters/hermes-local/
|
||||
COPY packages/adapters/openclaw-gateway/package.json packages/adapters/openclaw-gateway/
|
||||
COPY packages/adapters/opencode-local/package.json packages/adapters/opencode-local/
|
||||
COPY packages/adapters/pi-local/package.json packages/adapters/pi-local/
|
||||
|
||||
@@ -210,10 +210,10 @@ Paperclip UI can display both managed and native skills in one view.
|
||||
## Development
|
||||
|
||||
```bash
|
||||
git clone https://github.com/paperclipai/hermes-paperclip-adapter
|
||||
cd hermes-paperclip-adapter
|
||||
npm install
|
||||
npm run build
|
||||
git clone https://github.com/paperclipai/paperclip
|
||||
cd paperclip/packages/adapters/hermes-local
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
@@ -26,7 +26,11 @@
|
||||
".": "./src/index.ts",
|
||||
"./server": "./src/server/index.ts",
|
||||
"./ui": "./src/ui/index.ts",
|
||||
"./cli": "./src/cli/index.ts"
|
||||
"./cli": "./src/cli/index.ts",
|
||||
"./ui-parser": "./ui-parser.cjs"
|
||||
},
|
||||
"paperclip": {
|
||||
"adapterUiParser": "1.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -46,7 +50,8 @@
|
||||
"./cli": {
|
||||
"types": "./dist/cli/index.d.ts",
|
||||
"import": "./dist/cli/index.js"
|
||||
}
|
||||
},
|
||||
"./ui-parser": "./ui-parser.cjs"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -54,6 +59,7 @@
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"ui-parser.cjs",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
@@ -64,6 +70,8 @@
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:command-resolution": "vitest run src/server/command-resolution.test.ts",
|
||||
"prepack": "node scripts/prepare-publish-package.mjs",
|
||||
"postpack": "node scripts/restore-dev-package.mjs",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env node
|
||||
import { existsSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const packageDir = resolve(scriptDir, "..");
|
||||
const packageJsonPath = join(packageDir, "package.json");
|
||||
const devPackageJsonPath = join(packageDir, "package.dev.json");
|
||||
|
||||
function findRepoRoot(startDir) {
|
||||
let current = startDir;
|
||||
while (current !== dirname(current)) {
|
||||
if (existsSync(join(current, "pnpm-workspace.yaml"))) {
|
||||
return current;
|
||||
}
|
||||
current = dirname(current);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function readJson(filePath) {
|
||||
return JSON.parse(readFileSync(filePath, "utf8"));
|
||||
}
|
||||
|
||||
function collectWorkspaceVersions(repoRoot) {
|
||||
const roots = ["packages", "server", "ui", "cli"];
|
||||
const versions = new Map();
|
||||
|
||||
function walk(relDir) {
|
||||
const absDir = join(repoRoot, relDir);
|
||||
if (!existsSync(absDir)) return;
|
||||
|
||||
const pkgPath = join(absDir, "package.json");
|
||||
if (existsSync(pkgPath)) {
|
||||
const pkg = readJson(pkgPath);
|
||||
if (pkg.name && pkg.version) {
|
||||
versions.set(pkg.name, pkg.version);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of readdirSync(absDir, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") continue;
|
||||
walk(join(relDir, entry.name));
|
||||
}
|
||||
}
|
||||
|
||||
for (const root of roots) {
|
||||
walk(root);
|
||||
}
|
||||
|
||||
return versions;
|
||||
}
|
||||
|
||||
function rewriteWorkspaceDeps(deps, workspaceVersions) {
|
||||
if (!deps) return deps;
|
||||
return Object.fromEntries(
|
||||
Object.entries(deps).map(([name, version]) => {
|
||||
if (typeof version !== "string" || !version.startsWith("workspace:")) {
|
||||
return [name, version];
|
||||
}
|
||||
|
||||
const resolvedVersion = workspaceVersions.get(name);
|
||||
if (!resolvedVersion) {
|
||||
throw new Error(`Cannot resolve workspace dependency ${name} for publish package`);
|
||||
}
|
||||
return [name, resolvedVersion];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const pkg = readJson(packageJsonPath);
|
||||
const publishConfig = pkg.publishConfig ?? {};
|
||||
|
||||
if (existsSync(devPackageJsonPath)) {
|
||||
throw new Error(`Refusing to overwrite existing ${devPackageJsonPath}`);
|
||||
}
|
||||
|
||||
if (!publishConfig.exports) {
|
||||
throw new Error(`${pkg.name} is missing publishConfig.exports`);
|
||||
}
|
||||
|
||||
const repoRoot = findRepoRoot(packageDir);
|
||||
const workspaceVersions = repoRoot ? collectWorkspaceVersions(repoRoot) : new Map();
|
||||
|
||||
renameSync(packageJsonPath, devPackageJsonPath);
|
||||
|
||||
const nextPublishConfig = { ...publishConfig };
|
||||
delete nextPublishConfig.exports;
|
||||
delete nextPublishConfig.main;
|
||||
delete nextPublishConfig.types;
|
||||
|
||||
const publishPkg = {
|
||||
...pkg,
|
||||
exports: publishConfig.exports,
|
||||
main: publishConfig.main,
|
||||
types: publishConfig.types,
|
||||
publishConfig: nextPublishConfig,
|
||||
dependencies: rewriteWorkspaceDeps(pkg.dependencies, workspaceVersions),
|
||||
optionalDependencies: rewriteWorkspaceDeps(pkg.optionalDependencies, workspaceVersions),
|
||||
peerDependencies: rewriteWorkspaceDeps(pkg.peerDependencies, workspaceVersions),
|
||||
};
|
||||
|
||||
writeFileSync(packageJsonPath, `${JSON.stringify(publishPkg, null, 2)}\n`);
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env node
|
||||
import { existsSync, renameSync, rmSync } from "node:fs";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const packageDir = resolve(scriptDir, "..");
|
||||
const packageJsonPath = join(packageDir, "package.json");
|
||||
const devPackageJsonPath = join(packageDir, "package.dev.json");
|
||||
|
||||
if (existsSync(devPackageJsonPath)) {
|
||||
rmSync(packageJsonPath, { force: true });
|
||||
renameSync(devPackageJsonPath, packageJsonPath);
|
||||
}
|
||||
@@ -72,6 +72,7 @@ function parseToolCompletionLine(
|
||||
let verbAndDetail = durationMatch
|
||||
? cleaned.slice(0, cleaned.lastIndexOf(durationMatch[0])).trim()
|
||||
: cleaned;
|
||||
verbAndDetail = verbAndDetail.replace(/^\p{Emoji_Presentation}\s*/u, "");
|
||||
|
||||
// Check for error suffixes
|
||||
const hasError = /\[(?:exit \d+|error|full)\]/.test(verbAndDetail) ||
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
"use strict";
|
||||
|
||||
const TOOL_OUTPUT_PREFIX = "\u250a";
|
||||
|
||||
function stripKaomoji(text) {
|
||||
return text.replace(/[(][^()]{2,20}[)]\s*/gu, "").trim();
|
||||
}
|
||||
|
||||
function isAssistantToolLine(stripped) {
|
||||
return /^\u250a\s*\u{1f4ac}/u.test(stripped);
|
||||
}
|
||||
|
||||
function extractAssistantText(line) {
|
||||
return line.replace(/^[\s\u250a]*\u{1f4ac}\s*/u, "").trim();
|
||||
}
|
||||
|
||||
function parseToolCompletionLine(line) {
|
||||
let cleaned = line.trim().replace(/^\[done\]\s*/, "");
|
||||
if (!cleaned.startsWith(TOOL_OUTPUT_PREFIX)) return null;
|
||||
|
||||
cleaned = cleaned.slice(TOOL_OUTPUT_PREFIX.length);
|
||||
cleaned = stripKaomoji(cleaned).trim();
|
||||
|
||||
const durationMatch = cleaned.match(/([\d.]+s)\s*(?:\([\d.]+s\))?\s*$/);
|
||||
const duration = durationMatch ? durationMatch[1] : "";
|
||||
const verbAndDetail = durationMatch
|
||||
? cleaned.slice(0, cleaned.lastIndexOf(durationMatch[0])).trim()
|
||||
: cleaned;
|
||||
const detailWithoutEmoji = verbAndDetail.replace(/^\p{Emoji_Presentation}\s*/u, "");
|
||||
|
||||
const hasError = /\[(?:exit \d+|error|full)\]/.test(detailWithoutEmoji) ||
|
||||
/\[error\]\s*$/.test(cleaned);
|
||||
|
||||
const parts = detailWithoutEmoji.match(/^(\S+)\s+(.*)/);
|
||||
if (!parts) {
|
||||
return { name: "tool", detail: detailWithoutEmoji, duration, hasError };
|
||||
}
|
||||
|
||||
const verb = parts[1];
|
||||
const detail = parts[2].trim();
|
||||
const nameMap = {
|
||||
"$": "shell",
|
||||
exec: "shell",
|
||||
terminal: "shell",
|
||||
search: "search",
|
||||
fetch: "fetch",
|
||||
crawl: "crawl",
|
||||
navigate: "browser",
|
||||
snapshot: "browser",
|
||||
click: "browser",
|
||||
type: "browser",
|
||||
scroll: "browser",
|
||||
back: "browser",
|
||||
press: "browser",
|
||||
close: "browser",
|
||||
images: "browser",
|
||||
vision: "browser",
|
||||
read: "read",
|
||||
write: "write",
|
||||
patch: "patch",
|
||||
grep: "search",
|
||||
find: "search",
|
||||
plan: "plan",
|
||||
recall: "recall",
|
||||
proc: "process",
|
||||
delegate: "delegate",
|
||||
todo: "todo",
|
||||
memory: "memory",
|
||||
clarify: "clarify",
|
||||
session_search: "recall",
|
||||
code: "execute",
|
||||
execute: "execute",
|
||||
web_search: "search",
|
||||
web_extract: "fetch",
|
||||
browser_navigate: "browser",
|
||||
browser_click: "browser",
|
||||
browser_type: "browser",
|
||||
browser_snapshot: "browser",
|
||||
browser_vision: "browser",
|
||||
browser_scroll: "browser",
|
||||
browser_press: "browser",
|
||||
browser_back: "browser",
|
||||
browser_close: "browser",
|
||||
browser_get_images: "browser",
|
||||
read_file: "read",
|
||||
write_file: "write_file",
|
||||
search_files: "search",
|
||||
patch_file: "patch",
|
||||
execute_code: "execute",
|
||||
};
|
||||
|
||||
return {
|
||||
name: nameMap[verb.toLowerCase()] || verb,
|
||||
detail,
|
||||
duration,
|
||||
hasError,
|
||||
};
|
||||
}
|
||||
|
||||
let toolCallCounter = 0;
|
||||
|
||||
function syntheticToolUseId() {
|
||||
toolCallCounter += 1;
|
||||
return `hermes-tool-${toolCallCounter}`;
|
||||
}
|
||||
|
||||
function isThinkingLine(line) {
|
||||
return (
|
||||
line.includes("\u{1f4ad}") ||
|
||||
line.startsWith("<thinking>") ||
|
||||
line.startsWith("</thinking>") ||
|
||||
line.startsWith("Thinking:")
|
||||
);
|
||||
}
|
||||
|
||||
function parseStdoutLine(line, ts) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) return [];
|
||||
|
||||
if (trimmed.startsWith("[hermes]") || trimmed.startsWith("[paperclip]")) {
|
||||
return [{ kind: "system", ts, text: trimmed }];
|
||||
}
|
||||
|
||||
if (trimmed.startsWith("[tool]")) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (/^\[\d{4}-\d{2}-\d{2}T/.test(trimmed)) {
|
||||
return [{ kind: "stderr", ts, text: trimmed }];
|
||||
}
|
||||
|
||||
if (/^\p{Emoji_Presentation}\s*(Completed|Running|Error)?\s*$/u.test(trimmed)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (trimmed.startsWith("session_id:")) {
|
||||
return [{ kind: "system", ts, text: trimmed }];
|
||||
}
|
||||
|
||||
if (trimmed.includes(TOOL_OUTPUT_PREFIX)) {
|
||||
if (isAssistantToolLine(trimmed)) {
|
||||
return [{ kind: "assistant", ts, text: extractAssistantText(trimmed) }];
|
||||
}
|
||||
|
||||
const toolInfo = parseToolCompletionLine(trimmed);
|
||||
if (toolInfo) {
|
||||
const id = syntheticToolUseId();
|
||||
const detailText = toolInfo.duration
|
||||
? `${toolInfo.detail} ${toolInfo.duration}`
|
||||
: toolInfo.detail;
|
||||
|
||||
return [
|
||||
{
|
||||
kind: "tool_call",
|
||||
ts,
|
||||
name: toolInfo.name,
|
||||
input: { detail: toolInfo.detail },
|
||||
toolUseId: id,
|
||||
},
|
||||
{
|
||||
kind: "tool_result",
|
||||
ts,
|
||||
toolUseId: id,
|
||||
content: detailText,
|
||||
isError: toolInfo.hasError,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const escapedPrefix = TOOL_OUTPUT_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const stripped = trimmed
|
||||
.replace(/^\[done\]\s*/, "")
|
||||
.replace(new RegExp(`^${escapedPrefix}\\s*`), "")
|
||||
.trim();
|
||||
return [{ kind: "stdout", ts, text: stripped }];
|
||||
}
|
||||
|
||||
if (isThinkingLine(trimmed)) {
|
||||
return [{ kind: "thinking", ts, text: trimmed.replace(/^\u{1f4ad}\s*/u, "") }];
|
||||
}
|
||||
|
||||
if (
|
||||
trimmed.startsWith("Error:") ||
|
||||
trimmed.startsWith("ERROR:") ||
|
||||
trimmed.startsWith("Traceback")
|
||||
) {
|
||||
return [{ kind: "stderr", ts, text: trimmed }];
|
||||
}
|
||||
|
||||
return [{ kind: "assistant", ts, text: trimmed }];
|
||||
}
|
||||
|
||||
module.exports = { parseStdoutLine };
|
||||
Reference in new issue
Block a user