@paperclipai/hermes-paperclip-adapter 0.3.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/LICENSE +21 -0
- package/README.md +228 -0
- package/dist/cli/format-event.d.ts +14 -0
- package/dist/cli/format-event.d.ts.map +1 -0
- package/dist/cli/format-event.js +52 -0
- package/dist/cli/format-event.js.map +1 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +14 -0
- package/dist/index.test.js.map +1 -0
- package/dist/server/command-resolution.test.d.ts +2 -0
- package/dist/server/command-resolution.test.d.ts.map +1 -0
- package/dist/server/command-resolution.test.js +37 -0
- package/dist/server/command-resolution.test.js.map +1 -0
- package/dist/server/detect-model.d.ts +77 -0
- package/dist/server/detect-model.d.ts.map +1 -0
- package/dist/server/detect-model.js +158 -0
- package/dist/server/detect-model.js.map +1 -0
- package/dist/server/detect-model.test.d.ts +2 -0
- package/dist/server/detect-model.test.d.ts.map +1 -0
- package/dist/server/detect-model.test.js +166 -0
- package/dist/server/detect-model.test.js.map +1 -0
- package/dist/server/execute.d.ts +23 -0
- package/dist/server/execute.d.ts.map +1 -0
- package/dist/server/execute.js +482 -0
- package/dist/server/execute.js.map +1 -0
- package/dist/server/index.d.ts +16 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +43 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/skills.d.ts +8 -0
- package/dist/server/skills.d.ts.map +1 -0
- package/dist/server/skills.js +177 -0
- package/dist/server/skills.js.map +1 -0
- package/dist/server/test.d.ts +9 -0
- package/dist/server/test.d.ts.map +1 -0
- package/dist/server/test.js +341 -0
- package/dist/server/test.js.map +1 -0
- package/dist/shared/constants.d.ts +47 -0
- package/dist/shared/constants.d.ts.map +1 -0
- package/dist/shared/constants.js +91 -0
- package/dist/shared/constants.js.map +1 -0
- package/dist/ui/build-config.d.ts +17 -0
- package/dist/ui/build-config.d.ts.map +1 -0
- package/dist/ui/build-config.js +69 -0
- package/dist/ui/build-config.js.map +1 -0
- package/dist/ui/index.d.ts +7 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +7 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/parse-stdout.d.ts +25 -0
- package/dist/ui/parse-stdout.d.ts.map +1 -0
- package/dist/ui/parse-stdout.js +237 -0
- package/dist/ui/parse-stdout.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect the current model and provider from the user's Hermes config.
|
|
3
|
+
*
|
|
4
|
+
* Reads ~/.hermes/config.yaml and extracts the default model,
|
|
5
|
+
* provider, base_url, api_key presence, and api_mode settings.
|
|
6
|
+
*
|
|
7
|
+
* Also provides provider resolution logic that merges explicit config,
|
|
8
|
+
* Hermes config detection, and model-name prefix inference.
|
|
9
|
+
*/
|
|
10
|
+
import { readFile } from "node:fs/promises";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { MODEL_PREFIX_PROVIDER_HINTS, VALID_PROVIDERS } from "../shared/constants.js";
|
|
14
|
+
/**
|
|
15
|
+
* Read the Hermes config file and extract the default model config.
|
|
16
|
+
*/
|
|
17
|
+
export async function detectModel(configPath) {
|
|
18
|
+
const filePath = configPath ?? join(homedir(), ".hermes", "config.yaml");
|
|
19
|
+
let content;
|
|
20
|
+
try {
|
|
21
|
+
content = await readFile(filePath, "utf-8");
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return parseModelFromConfig(content);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parse model.default, model.provider, model.base_url, model.api_key, and model.api_mode
|
|
30
|
+
* from raw YAML content. Uses simple regex parsing to avoid a YAML dependency.
|
|
31
|
+
*/
|
|
32
|
+
export function parseModelFromConfig(content) {
|
|
33
|
+
const lines = content.split("\n");
|
|
34
|
+
let model = "";
|
|
35
|
+
let provider = "";
|
|
36
|
+
let baseUrl = "";
|
|
37
|
+
let hasApiKey = false;
|
|
38
|
+
let apiMode = "";
|
|
39
|
+
let inModelSection = false;
|
|
40
|
+
let modelSectionIndent = 0;
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
const trimmed = line.trimEnd();
|
|
43
|
+
const indent = line.length - line.trimStart().length;
|
|
44
|
+
// Track model: section (indent 0)
|
|
45
|
+
if (/^model:\s*$/.test(trimmed) && indent === 0) {
|
|
46
|
+
inModelSection = true;
|
|
47
|
+
modelSectionIndent = 0;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// We left the model section if indent drops back to the section level or below
|
|
51
|
+
if (inModelSection && indent <= modelSectionIndent && trimmed && !trimmed.startsWith("#")) {
|
|
52
|
+
inModelSection = false;
|
|
53
|
+
}
|
|
54
|
+
if (inModelSection) {
|
|
55
|
+
const match = trimmed.match(/^\s*(\w+)\s*:\s*(.+)$/);
|
|
56
|
+
if (match) {
|
|
57
|
+
const key = match[1];
|
|
58
|
+
const val = match[2].trim().replace(/#.*$/, "").trim().replace(/^['"]|['"]$/g, "");
|
|
59
|
+
if (key === "default")
|
|
60
|
+
model = val;
|
|
61
|
+
if (key === "provider")
|
|
62
|
+
provider = val;
|
|
63
|
+
if (key === "base_url")
|
|
64
|
+
baseUrl = val;
|
|
65
|
+
if (key === "api_key")
|
|
66
|
+
hasApiKey = val.length > 0;
|
|
67
|
+
if (key === "api_mode")
|
|
68
|
+
apiMode = val;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!model)
|
|
73
|
+
return null;
|
|
74
|
+
return { model, provider, baseUrl, hasApiKey, apiMode, source: "config" };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Infer a provider from the model name using prefix-based hints.
|
|
78
|
+
*
|
|
79
|
+
* For example:
|
|
80
|
+
* "gpt-5.4" → "copilot"
|
|
81
|
+
* "claude-sonnet-4" → "anthropic"
|
|
82
|
+
* "glm-5-turbo" → "zai"
|
|
83
|
+
*
|
|
84
|
+
* Returns undefined if no hint matches (caller should fall back to "auto").
|
|
85
|
+
*/
|
|
86
|
+
export function inferProviderFromModel(model) {
|
|
87
|
+
const lower = model.toLowerCase();
|
|
88
|
+
// Strip provider/ prefix if present (e.g. "anthropic/claude-sonnet-4")
|
|
89
|
+
const bareName = lower.includes("/") ? lower.split("/").pop() : lower;
|
|
90
|
+
for (const [prefix, hint] of MODEL_PREFIX_PROVIDER_HINTS) {
|
|
91
|
+
if (bareName.startsWith(prefix)) {
|
|
92
|
+
return hint;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Resolve the correct provider for a model, using a priority chain:
|
|
99
|
+
*
|
|
100
|
+
* 1. Explicit provider from adapterConfig (highest priority — user override)
|
|
101
|
+
* 2. Provider from Hermes config file — ONLY if the config model matches
|
|
102
|
+
* the requested model (otherwise the config provider is for a different model)
|
|
103
|
+
* 3. If Hermes config matches the requested model but uses runtime settings that
|
|
104
|
+
* the adapter cannot represent directly, return "auto" and let Hermes resolve it itself
|
|
105
|
+
* 4. Provider inferred from model name prefix
|
|
106
|
+
* 5. "auto" (let Hermes figure it out — lowest priority)
|
|
107
|
+
*
|
|
108
|
+
* Always returns a valid provider string.
|
|
109
|
+
* The `resolvedFrom` field indicates which source was used, useful for logging.
|
|
110
|
+
*/
|
|
111
|
+
export function resolveProvider(options) {
|
|
112
|
+
const { explicitProvider, detectedProvider, detectedModel, detectedBaseUrl, detectedHasApiKey, detectedApiMode, model, } = options;
|
|
113
|
+
// 1. Explicit provider from adapterConfig — user override, always wins
|
|
114
|
+
if (explicitProvider && VALID_PROVIDERS.includes(explicitProvider)) {
|
|
115
|
+
return { provider: explicitProvider, resolvedFrom: "adapterConfig" };
|
|
116
|
+
}
|
|
117
|
+
const supportedProviders = VALID_PROVIDERS;
|
|
118
|
+
const configMatchesRequestedModel = !!detectedModel &&
|
|
119
|
+
!!model &&
|
|
120
|
+
detectedModel.toLowerCase() === model.toLowerCase();
|
|
121
|
+
// 2. Provider from Hermes config file — but ONLY if the config model matches
|
|
122
|
+
// the requested model. Otherwise the config provider is for a different model
|
|
123
|
+
// and would cause exactly the kind of routing bug we're fixing.
|
|
124
|
+
if (configMatchesRequestedModel &&
|
|
125
|
+
!!detectedProvider &&
|
|
126
|
+
supportedProviders.includes(detectedProvider)) {
|
|
127
|
+
return { provider: detectedProvider, resolvedFrom: "hermesConfig" };
|
|
128
|
+
}
|
|
129
|
+
const hasRuntimeSignals = !!detectedBaseUrl || !!detectedHasApiKey || !!detectedApiMode;
|
|
130
|
+
// 3a. Matching Hermes config with an unsupported provider (for example "custom")
|
|
131
|
+
// should not fall through to model-name inference, because that can route to
|
|
132
|
+
// the wrong provider entirely. Defer back to Hermes's own runtime resolution.
|
|
133
|
+
if (configMatchesRequestedModel && !!detectedProvider && !supportedProviders.includes(detectedProvider)) {
|
|
134
|
+
return {
|
|
135
|
+
provider: "auto",
|
|
136
|
+
resolvedFrom: `hermesConfigUnsupported:${detectedProvider}`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// 3b. Matching Hermes config may omit provider entirely while still specifying
|
|
140
|
+
// enough runtime information (base_url, api_key, api_mode) for Hermes itself.
|
|
141
|
+
// In that case, also defer to Hermes instead of doing a wrong prefix inference.
|
|
142
|
+
if (configMatchesRequestedModel && !detectedProvider && hasRuntimeSignals) {
|
|
143
|
+
return {
|
|
144
|
+
provider: "auto",
|
|
145
|
+
resolvedFrom: "hermesConfigRuntime",
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// 4. Infer from model name prefix
|
|
149
|
+
if (model) {
|
|
150
|
+
const inferred = inferProviderFromModel(model);
|
|
151
|
+
if (inferred) {
|
|
152
|
+
return { provider: inferred, resolvedFrom: "modelInference" };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// 5. Let Hermes auto-detect
|
|
156
|
+
return { provider: "auto", resolvedFrom: "auto" };
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=detect-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-model.js","sourceRoot":"","sources":["../../src/server/detect-model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-model.test.d.ts","sourceRoot":"","sources":["../../src/server/detect-model.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, expect, test } from "vitest";
|
|
5
|
+
import { parseModelFromConfig, resolveProvider } from "./detect-model.js";
|
|
6
|
+
import { testEnvironment } from "./test.js";
|
|
7
|
+
const providerEnvKeys = [
|
|
8
|
+
"ANTHROPIC_API_KEY",
|
|
9
|
+
"OPENROUTER_API_KEY",
|
|
10
|
+
"OPENAI_API_KEY",
|
|
11
|
+
"ZAI_API_KEY",
|
|
12
|
+
"KIMI_API_KEY",
|
|
13
|
+
"MINIMAX_API_KEY",
|
|
14
|
+
];
|
|
15
|
+
const previousEnv = {
|
|
16
|
+
HOME: process.env.HOME,
|
|
17
|
+
USERPROFILE: process.env.USERPROFILE,
|
|
18
|
+
HOMEDRIVE: process.env.HOMEDRIVE,
|
|
19
|
+
HOMEPATH: process.env.HOMEPATH,
|
|
20
|
+
...Object.fromEntries(providerEnvKeys.map((key) => [key, process.env[key]])),
|
|
21
|
+
};
|
|
22
|
+
afterEach(async () => {
|
|
23
|
+
for (const [key, value] of Object.entries(previousEnv)) {
|
|
24
|
+
if (value === undefined) {
|
|
25
|
+
delete process.env[key];
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
process.env[key] = value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
test("parseModelFromConfig tracks api_key presence without exposing the raw secret", () => {
|
|
33
|
+
const parsed = parseModelFromConfig([
|
|
34
|
+
"model:",
|
|
35
|
+
" default: oca/gpt-5.4",
|
|
36
|
+
" provider: custom",
|
|
37
|
+
" base_url: https://example.invalid/litellm",
|
|
38
|
+
" api_key: super-secret-value",
|
|
39
|
+
"",
|
|
40
|
+
].join("\n"));
|
|
41
|
+
expect(parsed).toBeTruthy();
|
|
42
|
+
expect(parsed?.hasApiKey).toBe(true);
|
|
43
|
+
expect(Object.hasOwn(parsed ?? {}, "apiKey")).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
test("resolveProvider does not fall through to model inference when Hermes config provider is unsupported but matches the requested model", () => {
|
|
46
|
+
expect(resolveProvider({
|
|
47
|
+
explicitProvider: undefined,
|
|
48
|
+
detectedProvider: "custom",
|
|
49
|
+
detectedModel: "oca/gpt-5.4",
|
|
50
|
+
detectedBaseUrl: "https://example.invalid/litellm",
|
|
51
|
+
detectedHasApiKey: true,
|
|
52
|
+
model: "oca/gpt-5.4",
|
|
53
|
+
})).toEqual({
|
|
54
|
+
provider: "auto",
|
|
55
|
+
resolvedFrom: "hermesConfigUnsupported:custom",
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
test("resolveProvider also defers to Hermes runtime when the matching config omits provider but includes runtime signals", () => {
|
|
59
|
+
expect(resolveProvider({
|
|
60
|
+
explicitProvider: undefined,
|
|
61
|
+
detectedProvider: "",
|
|
62
|
+
detectedModel: "oca/gpt-5.4",
|
|
63
|
+
detectedBaseUrl: "https://example.invalid/litellm",
|
|
64
|
+
detectedHasApiKey: true,
|
|
65
|
+
model: "oca/gpt-5.4",
|
|
66
|
+
})).toEqual({
|
|
67
|
+
provider: "auto",
|
|
68
|
+
resolvedFrom: "hermesConfigRuntime",
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
test("resolveProvider still infers from the requested model when Hermes config is for a different model", () => {
|
|
72
|
+
expect(resolveProvider({
|
|
73
|
+
explicitProvider: undefined,
|
|
74
|
+
detectedProvider: "custom",
|
|
75
|
+
detectedModel: "oca/gpt-5.4",
|
|
76
|
+
detectedBaseUrl: "https://example.invalid/litellm",
|
|
77
|
+
detectedHasApiKey: true,
|
|
78
|
+
model: "claude-sonnet-4",
|
|
79
|
+
})).toEqual({
|
|
80
|
+
provider: "anthropic",
|
|
81
|
+
resolvedFrom: "modelInference",
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
async function withHermesHomeConfig(configLines, fn) {
|
|
85
|
+
const tempHome = await mkdtemp(join(tmpdir(), "hermes-paperclip-adapter-"));
|
|
86
|
+
const hermesDir = join(tempHome, ".hermes");
|
|
87
|
+
const configPath = join(hermesDir, "config.yaml");
|
|
88
|
+
await mkdir(hermesDir, { recursive: true });
|
|
89
|
+
await writeFile(configPath, `${configLines.join("\n")}\n`, "utf8");
|
|
90
|
+
process.env.HOME = tempHome;
|
|
91
|
+
process.env.USERPROFILE = tempHome;
|
|
92
|
+
delete process.env.HOMEDRIVE;
|
|
93
|
+
delete process.env.HOMEPATH;
|
|
94
|
+
for (const key of providerEnvKeys) {
|
|
95
|
+
delete process.env[key];
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
await fn();
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
await rm(tempHome, { recursive: true, force: true });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
test("testEnvironment does not warn about missing API keys when Hermes config provides a supported provider api_key", async () => {
|
|
105
|
+
await withHermesHomeConfig([
|
|
106
|
+
"model:",
|
|
107
|
+
" default: openrouter/gpt-4.1-mini",
|
|
108
|
+
" provider: openrouter",
|
|
109
|
+
" api_key: test-secret",
|
|
110
|
+
], async () => {
|
|
111
|
+
const result = await testEnvironment({
|
|
112
|
+
companyId: "company-test",
|
|
113
|
+
adapterType: "hermes_local",
|
|
114
|
+
config: {
|
|
115
|
+
hermesCommand: "python3",
|
|
116
|
+
model: "openrouter/gpt-4.1-mini",
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
const codes = result.checks.map((check) => check.code);
|
|
120
|
+
expect(codes.includes("hermes_no_api_keys")).toBe(false);
|
|
121
|
+
expect(result.status).toBe("pass");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
test("testEnvironment describes provider-omitted runtime config without inventing provider auto", async () => {
|
|
125
|
+
await withHermesHomeConfig([
|
|
126
|
+
"model:",
|
|
127
|
+
" default: oca/gpt-5.4",
|
|
128
|
+
" base_url: https://example.invalid/litellm",
|
|
129
|
+
" api_key: test-secret",
|
|
130
|
+
], async () => {
|
|
131
|
+
const result = await testEnvironment({
|
|
132
|
+
companyId: "company-test",
|
|
133
|
+
adapterType: "hermes_local",
|
|
134
|
+
config: {
|
|
135
|
+
hermesCommand: "python3",
|
|
136
|
+
model: "oca/gpt-5.4",
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
const apiKeyCheck = result.checks.find((check) => check.code === "hermes_api_key_in_config");
|
|
140
|
+
expect(apiKeyCheck).toBeTruthy();
|
|
141
|
+
expect(apiKeyCheck?.message).toMatch(/without an explicit provider/i);
|
|
142
|
+
expect(apiKeyCheck?.message).not.toMatch(/provider "auto"/i);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
test("testEnvironment does not warn about missing API keys when Hermes config provides a custom provider base_url and api_key", async () => {
|
|
146
|
+
await withHermesHomeConfig([
|
|
147
|
+
"model:",
|
|
148
|
+
" default: oca/gpt-5.4",
|
|
149
|
+
" provider: custom",
|
|
150
|
+
" base_url: https://example.invalid/litellm",
|
|
151
|
+
" api_key: test-secret",
|
|
152
|
+
], async () => {
|
|
153
|
+
const result = await testEnvironment({
|
|
154
|
+
companyId: "company-test",
|
|
155
|
+
adapterType: "hermes_local",
|
|
156
|
+
config: {
|
|
157
|
+
hermesCommand: "python3",
|
|
158
|
+
model: "oca/gpt-5.4",
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
const codes = result.checks.map((check) => check.code);
|
|
162
|
+
expect(codes.includes("hermes_no_api_keys")).toBe(false);
|
|
163
|
+
expect(result.status).toBe("pass");
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
//# sourceMappingURL=detect-model.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-model.test.js","sourceRoot":"","sources":["../../src/server/detect-model.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side execution logic for the Hermes Agent adapter.
|
|
3
|
+
*
|
|
4
|
+
* Spawns `hermes chat -q "..." -Q` as a child process, streams output,
|
|
5
|
+
* and returns structured results to Paperclip.
|
|
6
|
+
*
|
|
7
|
+
* Verified CLI flags (hermes chat):
|
|
8
|
+
* -q/--query single query (non-interactive)
|
|
9
|
+
* -Q/--quiet quiet mode (no banner/spinner, only response + session_id)
|
|
10
|
+
* -m/--model model name (e.g. anthropic/claude-sonnet-4)
|
|
11
|
+
* -t/--toolsets comma-separated toolsets to enable
|
|
12
|
+
* --provider inference provider (auto, openrouter, nous, etc.)
|
|
13
|
+
* -r/--resume resume session by ID
|
|
14
|
+
* -w/--worktree isolated git worktree
|
|
15
|
+
* -v/--verbose verbose output
|
|
16
|
+
* --checkpoints filesystem checkpoints
|
|
17
|
+
* --yolo bypass dangerous-command approval prompts (agents have no TTY)
|
|
18
|
+
* --source session source tag for filtering
|
|
19
|
+
*/
|
|
20
|
+
import type { AdapterExecutionContext, AdapterExecutionResult } from "@paperclipai/adapter-utils";
|
|
21
|
+
export declare function resolveHermesCommand(config: Record<string, unknown>): string;
|
|
22
|
+
export declare function execute(ctx: AdapterExecutionContext): Promise<AdapterExecutionResult>;
|
|
23
|
+
//# sourceMappingURL=execute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/server/execute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,KAAK,EACV,uBAAuB,EACvB,sBAAsB,EAEvB,MAAM,4BAA4B,CAAC;AAyCpC,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAE5E;AAiQD,wBAAsB,OAAO,CAC3B,GAAG,EAAE,uBAAuB,GAC3B,OAAO,CAAC,sBAAsB,CAAC,CAsQjC"}
|