refactor(workflow-utils): reorganize — roles top-level, shared internals in shared/ #228
@@ -1,7 +1,7 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { z } from "zod";
|
||||
|
||||
import { llmExtract } from "../llm-extract.js";
|
||||
import { llmExtract } from "../shared/llm-extract.js";
|
||||
|
||||
describe("llmExtract", () => {
|
||||
afterEach(() => {
|
||||
|
||||
@@ -3,12 +3,10 @@ import { z } from "zod";
|
||||
|
||||
import { START } from "@uncaged/nerve-core";
|
||||
|
||||
import {
|
||||
createCursorRole,
|
||||
createHermesRole,
|
||||
createLlmRole,
|
||||
createReActRole,
|
||||
} from "../role-factories.js";
|
||||
import { createCursorRole } from "../role-cursor.js";
|
||||
import { createHermesRole } from "../role-hermes.js";
|
||||
import { createLlmRole } from "../role-llm.js";
|
||||
import { createReActRole } from "../role-react.js";
|
||||
|
||||
function startFrame(dryRun: boolean, threadId: string) {
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { z } from "zod";
|
||||
|
||||
import { schemaDefaults } from "../schema-defaults.js";
|
||||
import { schemaDefaults } from "../shared/schema-defaults.js";
|
||||
|
||||
describe("schemaDefaults", () => {
|
||||
it("fills nested objects with primitive placeholders", () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { spawnSafe } from "../spawn-safe.js";
|
||||
import { spawnSafe } from "../shared/spawn-safe.js";
|
||||
|
||||
describe("spawnSafe", () => {
|
||||
it("passes argv literally without shell interpretation (injection-safe)", async () => {
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import type { HermesRoleDefaults, HermesRoleRequired } from "./role-types.js";
|
||||
|
||||
const HERMES_DEFAULTS: HermesRoleDefaults = {
|
||||
model: null,
|
||||
provider: null,
|
||||
skills: [],
|
||||
quiet: true,
|
||||
maxTurns: 90,
|
||||
env: {},
|
||||
timeoutMs: 600_000,
|
||||
};
|
||||
|
||||
export function resolveHermesOptions<T>(
|
||||
options: HermesRoleRequired<T> & Partial<HermesRoleDefaults>,
|
||||
): HermesRoleDefaults {
|
||||
const d = HERMES_DEFAULTS;
|
||||
return {
|
||||
model: "model" in options && options.model !== undefined ? options.model : d.model,
|
||||
provider:
|
||||
"provider" in options && options.provider !== undefined ? options.provider : d.provider,
|
||||
skills: "skills" in options && options.skills !== undefined ? options.skills : d.skills,
|
||||
quiet: "quiet" in options && options.quiet !== undefined ? options.quiet : d.quiet,
|
||||
maxTurns:
|
||||
"maxTurns" in options && options.maxTurns !== undefined ? options.maxTurns : d.maxTurns,
|
||||
env: "env" in options && options.env !== undefined ? options.env : d.env,
|
||||
timeoutMs:
|
||||
"timeoutMs" in options && options.timeoutMs !== undefined ? options.timeoutMs : d.timeoutMs,
|
||||
};
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
// Primary API — role factory templates
|
||||
export {
|
||||
createCursorRole,
|
||||
createHermesRole,
|
||||
createLlmRole,
|
||||
createReActRole,
|
||||
} from "./role-factories.js";
|
||||
export { createCursorRole } from "./role-cursor.js";
|
||||
export { createHermesRole } from "./role-hermes.js";
|
||||
export { createLlmRole } from "./role-llm.js";
|
||||
export { createReActRole } from "./role-react.js";
|
||||
export {
|
||||
nerveAgentContext,
|
||||
readNerveYaml,
|
||||
type NerveYamlError,
|
||||
type ReadNerveYamlOptions,
|
||||
} from "./context.js";
|
||||
export { isDryRun } from "./start-step.js";
|
||||
} from "./shared/context.js";
|
||||
export { isDryRun } from "./role-types.js";
|
||||
export {
|
||||
nerveCommandEnv,
|
||||
spawnSafe,
|
||||
@@ -19,8 +17,8 @@ export {
|
||||
type SpawnError,
|
||||
type SpawnResult,
|
||||
type SpawnSafeOptions,
|
||||
} from "./spawn-safe.js";
|
||||
export type { LlmError, LlmProvider } from "./llm-extract.js";
|
||||
} from "./shared/spawn-safe.js";
|
||||
export type { LlmError, LlmProvider } from "./shared/llm-extract.js";
|
||||
export type {
|
||||
CliPromptFn,
|
||||
CursorRoleDefaults,
|
||||
@@ -35,4 +33,4 @@ export type {
|
||||
ReActRoleRequired,
|
||||
ReActTool,
|
||||
} from "./role-types.js";
|
||||
export type { LlmChatError } from "./llm-chat.js";
|
||||
export type { LlmChatError } from "./shared/llm-chat.js";
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import type { Role } from "@uncaged/nerve-core";
|
||||
|
||||
import type { CursorRoleDefaults, CursorRoleRequired } from "./role-types.js";
|
||||
import { isDryRun } from "./role-types.js";
|
||||
import { type CursorAgentMode, cursorAgent } from "./shared/cursor-agent.js";
|
||||
import { formatLlmError } from "./shared/format-error.js";
|
||||
import { llmExtract } from "./shared/llm-extract.js";
|
||||
import type { SpawnEnv } from "./shared/spawn-safe.js";
|
||||
|
||||
const CURSOR_DEFAULTS: CursorRoleDefaults = {
|
||||
mode: "default",
|
||||
model: "auto",
|
||||
env: {},
|
||||
timeoutMs: 300_000,
|
||||
};
|
||||
|
||||
function pick<T>(opts: Record<string, unknown>, key: string, fallback: T): T {
|
||||
if (key in opts && opts[key] !== undefined) {
|
||||
return opts[key] as T;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* `cursor-agent` + `llmExtract` to produce `RoleResult<T>`. CLI agent returns
|
||||
* a single string; structured meta is read via a cheap follow-up `llmExtract`.
|
||||
*/
|
||||
export function createCursorRole<T>(
|
||||
options: CursorRoleRequired<T> & Partial<CursorRoleDefaults>,
|
||||
): Role<T> {
|
||||
return async (start, _messages) => {
|
||||
const dry = isDryRun(start);
|
||||
const d = CURSOR_DEFAULTS;
|
||||
const mode = pick<CursorAgentMode>(options as Record<string, unknown>, "mode", d.mode);
|
||||
const model = pick<string>(options as Record<string, unknown>, "model", d.model);
|
||||
const env = pick<SpawnEnv>(options as Record<string, unknown>, "env", d.env);
|
||||
const timeoutMs = pick<number>(options as Record<string, unknown>, "timeoutMs", d.timeoutMs);
|
||||
const prompt = await options.prompt(start.meta.threadId);
|
||||
const run = await cursorAgent({
|
||||
prompt,
|
||||
mode,
|
||||
model,
|
||||
cwd: options.cwd,
|
||||
env: Object.keys(env).length === 0 ? null : env,
|
||||
timeoutMs,
|
||||
dryRun: dry,
|
||||
});
|
||||
if (!run.ok) {
|
||||
const e = run.error;
|
||||
if (e.kind === "non_zero_exit") {
|
||||
throw new Error(
|
||||
`cursor-agent: exitCode=${e.exitCode} stdout=${e.stdout} stderr=${e.stderr}`,
|
||||
);
|
||||
}
|
||||
if (e.kind === "timeout") {
|
||||
throw new Error("cursor-agent: timeout");
|
||||
}
|
||||
throw new Error(`cursor-agent: ${e.message}`);
|
||||
}
|
||||
const text = run.value;
|
||||
const metaR = await llmExtract({
|
||||
text,
|
||||
schema: options.extract.schema,
|
||||
provider: options.extract.provider,
|
||||
dryRun: dry,
|
||||
});
|
||||
if (!metaR.ok) {
|
||||
throw new Error(`llmExtract: ${formatLlmError(metaR.error)}`);
|
||||
}
|
||||
return { content: text, meta: metaR.value };
|
||||
};
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
import type { Role } from "@uncaged/nerve-core";
|
||||
|
||||
import { cursorAgent } from "./cursor-agent.js";
|
||||
import type { CursorAgentMode } from "./cursor-agent.js";
|
||||
import { hermesAgent } from "./hermes-agent.js";
|
||||
import { resolveHermesOptions } from "./hermes-options.js";
|
||||
import { type LlmChatError, chatCompletionText, reActIterativeChat } from "./llm-chat.js";
|
||||
import { type LlmError, llmExtract } from "./llm-extract.js";
|
||||
import type {
|
||||
CursorRoleDefaults,
|
||||
CursorRoleRequired,
|
||||
HermesRoleDefaults,
|
||||
HermesRoleRequired,
|
||||
LlmMessage,
|
||||
LlmRoleRequired,
|
||||
ReActRoleDefaults,
|
||||
ReActRoleRequired,
|
||||
} from "./role-types.js";
|
||||
import type { SpawnEnv } from "./spawn-safe.js";
|
||||
import { isDryRun } from "./start-step.js";
|
||||
|
||||
const CURSOR_DEFAULTS: CursorRoleDefaults = {
|
||||
mode: "default",
|
||||
model: "auto",
|
||||
env: {},
|
||||
timeoutMs: 300_000,
|
||||
};
|
||||
|
||||
const REACT_DEFAULTS: ReActRoleDefaults = {
|
||||
maxIterations: 10,
|
||||
};
|
||||
|
||||
function mergeMode(
|
||||
o: CursorRoleRequired<unknown> & Partial<CursorRoleDefaults>,
|
||||
d: CursorRoleDefaults,
|
||||
): CursorAgentMode {
|
||||
if ("mode" in o && o.mode !== undefined) {
|
||||
return o.mode;
|
||||
}
|
||||
return d.mode;
|
||||
}
|
||||
|
||||
function mergeCursorModel(
|
||||
o: CursorRoleRequired<unknown> & Partial<CursorRoleDefaults>,
|
||||
d: CursorRoleDefaults,
|
||||
): string {
|
||||
if ("model" in o && o.model !== undefined) {
|
||||
return o.model;
|
||||
}
|
||||
return d.model;
|
||||
}
|
||||
|
||||
function mergeCursorEnv(
|
||||
o: CursorRoleRequired<unknown> & Partial<CursorRoleDefaults>,
|
||||
d: CursorRoleDefaults,
|
||||
): SpawnEnv {
|
||||
if ("env" in o && o.env !== undefined) {
|
||||
return o.env;
|
||||
}
|
||||
return d.env;
|
||||
}
|
||||
|
||||
function mergeCursorTimeout(
|
||||
o: CursorRoleRequired<unknown> & Partial<CursorRoleDefaults>,
|
||||
d: CursorRoleDefaults,
|
||||
): number {
|
||||
if ("timeoutMs" in o && o.timeoutMs !== undefined) {
|
||||
return o.timeoutMs;
|
||||
}
|
||||
return d.timeoutMs;
|
||||
}
|
||||
|
||||
function formatLlmError(e: LlmError | LlmChatError): string {
|
||||
return JSON.stringify(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* `cursor-agent` + `llmExtract` to produce `RoleResult<T>`. CLI agent returns
|
||||
* a single string; structured meta is read via a cheap follow-up `llmExtract`.
|
||||
*/
|
||||
export function createCursorRole<T>(
|
||||
options: CursorRoleRequired<T> & Partial<CursorRoleDefaults>,
|
||||
): Role<T> {
|
||||
return async (start, _messages) => {
|
||||
const dry = isDryRun(start);
|
||||
const d = CURSOR_DEFAULTS;
|
||||
const mode = mergeMode(options, d);
|
||||
const model = mergeCursorModel(options, d);
|
||||
const env = mergeCursorEnv(options, d);
|
||||
const timeoutMs = mergeCursorTimeout(options, d);
|
||||
const prompt = await options.prompt(start.meta.threadId);
|
||||
const run = await cursorAgent({
|
||||
prompt,
|
||||
mode,
|
||||
model,
|
||||
cwd: options.cwd,
|
||||
env: Object.keys(env).length === 0 ? null : env,
|
||||
timeoutMs,
|
||||
dryRun: dry,
|
||||
});
|
||||
if (!run.ok) {
|
||||
const e = run.error;
|
||||
if (e.kind === "non_zero_exit") {
|
||||
throw new Error(
|
||||
`cursor-agent: exitCode=${e.exitCode} stdout=${e.stdout} stderr=${e.stderr}`,
|
||||
);
|
||||
}
|
||||
if (e.kind === "timeout") {
|
||||
throw new Error("cursor-agent: timeout");
|
||||
}
|
||||
throw new Error(`cursor-agent: ${e.message}`);
|
||||
}
|
||||
const text = run.value;
|
||||
const metaR = await llmExtract({
|
||||
text,
|
||||
schema: options.extract.schema,
|
||||
provider: options.extract.provider,
|
||||
dryRun: dry,
|
||||
});
|
||||
if (!metaR.ok) {
|
||||
throw new Error(`llmExtract: ${formatLlmError(metaR.error)}`);
|
||||
}
|
||||
return { content: text, meta: metaR.value };
|
||||
};
|
||||
}
|
||||
|
||||
export function createHermesRole<T>(
|
||||
options: HermesRoleRequired<T> & Partial<HermesRoleDefaults>,
|
||||
): Role<T> {
|
||||
return async (start, _messages) => {
|
||||
const dry = isDryRun(start);
|
||||
const h = resolveHermesOptions(options);
|
||||
const prompt = await options.prompt(start.meta.threadId);
|
||||
const run = await hermesAgent({
|
||||
prompt,
|
||||
model: h.model,
|
||||
provider: h.provider,
|
||||
skills: h.skills,
|
||||
quiet: h.quiet,
|
||||
maxTurns: h.maxTurns,
|
||||
env: Object.keys(h.env).length === 0 ? null : h.env,
|
||||
timeoutMs: h.timeoutMs,
|
||||
dryRun: dry,
|
||||
});
|
||||
if (!run.ok) {
|
||||
const e = run.error;
|
||||
if (e.kind === "non_zero_exit") {
|
||||
throw new Error(`hermes: exitCode=${e.exitCode} stdout=${e.stdout} stderr=${e.stderr}`);
|
||||
}
|
||||
if (e.kind === "timeout") {
|
||||
throw new Error("hermes: timeout");
|
||||
}
|
||||
throw new Error(`hermes: ${e.message}`);
|
||||
}
|
||||
const text = run.value;
|
||||
const metaR = await llmExtract({
|
||||
text,
|
||||
schema: options.extract.schema,
|
||||
provider: options.extract.provider,
|
||||
dryRun: dry,
|
||||
});
|
||||
if (!metaR.ok) {
|
||||
throw new Error(`llmExtract: ${formatLlmError(metaR.error)}`);
|
||||
}
|
||||
return { content: text, meta: metaR.value };
|
||||
};
|
||||
}
|
||||
|
||||
export function createLlmRole<T>(options: LlmRoleRequired<T>): Role<T> {
|
||||
return async (start, _messages) => {
|
||||
const dry = isDryRun(start);
|
||||
const messages: LlmMessage[] = await options.prompt(start.meta.threadId);
|
||||
const result = await chatCompletionText({ provider: options.provider, messages });
|
||||
if (!result.ok) {
|
||||
throw new Error(`llm: ${formatLlmError(result.error)}`);
|
||||
}
|
||||
const text = result.value;
|
||||
const metaR = await llmExtract({
|
||||
text,
|
||||
schema: options.extract.schema,
|
||||
provider: options.extract.provider,
|
||||
dryRun: dry,
|
||||
});
|
||||
if (!metaR.ok) {
|
||||
throw new Error(`llmExtract: ${formatLlmError(metaR.error)}`);
|
||||
}
|
||||
return { content: text, meta: metaR.value };
|
||||
};
|
||||
}
|
||||
|
||||
export function createReActRole<T>(
|
||||
options: ReActRoleRequired<T> & Partial<ReActRoleDefaults>,
|
||||
): Role<T> {
|
||||
return async (start, _messages) => {
|
||||
const dry = isDryRun(start);
|
||||
const def = REACT_DEFAULTS;
|
||||
const maxIt =
|
||||
"maxIterations" in options && options.maxIterations !== undefined
|
||||
? options.maxIterations
|
||||
: def.maxIterations;
|
||||
const messages: LlmMessage[] = await options.prompt(start.meta.threadId);
|
||||
const result = await reActIterativeChat({
|
||||
provider: options.provider,
|
||||
tools: options.tools,
|
||||
messages,
|
||||
maxIterations: maxIt,
|
||||
});
|
||||
if (!result.ok) {
|
||||
throw new Error(`react: ${formatLlmError(result.error)}`);
|
||||
}
|
||||
const text = result.value;
|
||||
const metaR = await llmExtract({
|
||||
text,
|
||||
schema: options.extract.schema,
|
||||
provider: options.extract.provider,
|
||||
dryRun: dry,
|
||||
});
|
||||
if (!metaR.ok) {
|
||||
throw new Error(`llmExtract: ${formatLlmError(metaR.error)}`);
|
||||
}
|
||||
return { content: text, meta: metaR.value };
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { Role } from "@uncaged/nerve-core";
|
||||
|
||||
import type { HermesRoleDefaults, HermesRoleRequired } from "./role-types.js";
|
||||
import { isDryRun } from "./role-types.js";
|
||||
import { formatLlmError } from "./shared/format-error.js";
|
||||
import { hermesAgent, resolveHermesOptions } from "./shared/hermes-agent.js";
|
||||
import { llmExtract } from "./shared/llm-extract.js";
|
||||
|
||||
export function createHermesRole<T>(
|
||||
options: HermesRoleRequired<T> & Partial<HermesRoleDefaults>,
|
||||
): Role<T> {
|
||||
return async (start, _messages) => {
|
||||
const dry = isDryRun(start);
|
||||
const h = resolveHermesOptions(options);
|
||||
const prompt = await options.prompt(start.meta.threadId);
|
||||
const run = await hermesAgent({
|
||||
prompt,
|
||||
model: h.model,
|
||||
provider: h.provider,
|
||||
skills: h.skills,
|
||||
quiet: h.quiet,
|
||||
maxTurns: h.maxTurns,
|
||||
env: Object.keys(h.env).length === 0 ? null : h.env,
|
||||
timeoutMs: h.timeoutMs,
|
||||
dryRun: dry,
|
||||
});
|
||||
if (!run.ok) {
|
||||
const e = run.error;
|
||||
if (e.kind === "non_zero_exit") {
|
||||
throw new Error(`hermes: exitCode=${e.exitCode} stdout=${e.stdout} stderr=${e.stderr}`);
|
||||
}
|
||||
if (e.kind === "timeout") {
|
||||
throw new Error("hermes: timeout");
|
||||
}
|
||||
throw new Error(`hermes: ${e.message}`);
|
||||
}
|
||||
const text = run.value;
|
||||
const metaR = await llmExtract({
|
||||
text,
|
||||
schema: options.extract.schema,
|
||||
provider: options.extract.provider,
|
||||
dryRun: dry,
|
||||
});
|
||||
if (!metaR.ok) {
|
||||
throw new Error(`llmExtract: ${formatLlmError(metaR.error)}`);
|
||||
}
|
||||
return { content: text, meta: metaR.value };
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { Role } from "@uncaged/nerve-core";
|
||||
|
||||
import type { LlmMessage, LlmRoleRequired } from "./role-types.js";
|
||||
import { isDryRun } from "./role-types.js";
|
||||
import { formatLlmError } from "./shared/format-error.js";
|
||||
import { chatCompletionText } from "./shared/llm-chat.js";
|
||||
import { llmExtract } from "./shared/llm-extract.js";
|
||||
|
||||
export function createLlmRole<T>(options: LlmRoleRequired<T>): Role<T> {
|
||||
return async (start, _messages) => {
|
||||
const dry = isDryRun(start);
|
||||
const messages: LlmMessage[] = await options.prompt(start.meta.threadId);
|
||||
const result = await chatCompletionText({ provider: options.provider, messages });
|
||||
if (!result.ok) {
|
||||
throw new Error(`llm: ${formatLlmError(result.error)}`);
|
||||
}
|
||||
const text = result.value;
|
||||
const metaR = await llmExtract({
|
||||
text,
|
||||
schema: options.extract.schema,
|
||||
provider: options.extract.provider,
|
||||
dryRun: dry,
|
||||
});
|
||||
if (!metaR.ok) {
|
||||
throw new Error(`llmExtract: ${formatLlmError(metaR.error)}`);
|
||||
}
|
||||
return { content: text, meta: metaR.value };
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { Role } from "@uncaged/nerve-core";
|
||||
|
||||
import type { LlmMessage, ReActRoleDefaults, ReActRoleRequired } from "./role-types.js";
|
||||
import { isDryRun } from "./role-types.js";
|
||||
import { formatLlmError } from "./shared/format-error.js";
|
||||
import { reActIterativeChat } from "./shared/llm-chat.js";
|
||||
import { llmExtract } from "./shared/llm-extract.js";
|
||||
|
||||
const REACT_DEFAULTS: ReActRoleDefaults = {
|
||||
maxIterations: 10,
|
||||
};
|
||||
|
||||
export function createReActRole<T>(
|
||||
options: ReActRoleRequired<T> & Partial<ReActRoleDefaults>,
|
||||
): Role<T> {
|
||||
return async (start, _messages) => {
|
||||
const dry = isDryRun(start);
|
||||
const def = REACT_DEFAULTS;
|
||||
const maxIt =
|
||||
"maxIterations" in options && options.maxIterations !== undefined
|
||||
? options.maxIterations
|
||||
: def.maxIterations;
|
||||
const messages: LlmMessage[] = await options.prompt(start.meta.threadId);
|
||||
const result = await reActIterativeChat({
|
||||
provider: options.provider,
|
||||
tools: options.tools,
|
||||
messages,
|
||||
maxIterations: maxIt,
|
||||
});
|
||||
if (!result.ok) {
|
||||
throw new Error(`react: ${formatLlmError(result.error)}`);
|
||||
}
|
||||
const text = result.value;
|
||||
const metaR = await llmExtract({
|
||||
text,
|
||||
schema: options.extract.schema,
|
||||
provider: options.extract.provider,
|
||||
dryRun: dry,
|
||||
});
|
||||
if (!metaR.ok) {
|
||||
throw new Error(`llmExtract: ${formatLlmError(metaR.error)}`);
|
||||
}
|
||||
return { content: text, meta: metaR.value };
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
import type { StartStep } from "@uncaged/nerve-core";
|
||||
import type { z } from "zod";
|
||||
|
||||
import type { LlmProvider } from "./llm-extract.js";
|
||||
import type { SpawnEnv } from "./spawn-safe.js";
|
||||
import type { LlmProvider } from "./shared/llm-extract.js";
|
||||
import type { SpawnEnv } from "./shared/spawn-safe.js";
|
||||
|
||||
/** Returns the thread-level dry-run flag from the workflow start frame. */
|
||||
export function isDryRun(start: StartStep): boolean {
|
||||
return start.meta.dryRun;
|
||||
}
|
||||
|
||||
export type CliPromptFn = (threadId: string) => Promise<string>;
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { LlmChatError } from "./llm-chat.js";
|
||||
import type { LlmError } from "./llm-extract.js";
|
||||
|
||||
export function formatLlmError(e: LlmError | LlmChatError): string {
|
||||
return JSON.stringify(e);
|
||||
}
|
||||
+31
@@ -1,5 +1,6 @@
|
||||
import { type Result, ok } from "@uncaged/nerve-core";
|
||||
|
||||
import type { HermesRoleDefaults, HermesRoleRequired } from "../role-types.js";
|
||||
import { type SpawnEnv, type SpawnError, spawnSafe } from "./spawn-safe.js";
|
||||
|
||||
/**
|
||||
@@ -64,3 +65,33 @@ export async function hermesAgent(
|
||||
}
|
||||
return ok(run.value.stdout);
|
||||
}
|
||||
|
||||
// --- Hermes options resolution (absorbed from hermes-options.ts) ---
|
||||
|
||||
const HERMES_DEFAULTS: HermesRoleDefaults = {
|
||||
model: null,
|
||||
provider: null,
|
||||
skills: [],
|
||||
quiet: true,
|
||||
maxTurns: 90,
|
||||
env: {},
|
||||
timeoutMs: 600_000,
|
||||
};
|
||||
|
||||
export function resolveHermesOptions<T>(
|
||||
options: HermesRoleRequired<T> & Partial<HermesRoleDefaults>,
|
||||
): HermesRoleDefaults {
|
||||
const d = HERMES_DEFAULTS;
|
||||
return {
|
||||
model: "model" in options && options.model !== undefined ? options.model : d.model,
|
||||
provider:
|
||||
"provider" in options && options.provider !== undefined ? options.provider : d.provider,
|
||||
skills: "skills" in options && options.skills !== undefined ? options.skills : d.skills,
|
||||
quiet: "quiet" in options && options.quiet !== undefined ? options.quiet : d.quiet,
|
||||
maxTurns:
|
||||
"maxTurns" in options && options.maxTurns !== undefined ? options.maxTurns : d.maxTurns,
|
||||
env: "env" in options && options.env !== undefined ? options.env : d.env,
|
||||
timeoutMs:
|
||||
"timeoutMs" in options && options.timeoutMs !== undefined ? options.timeoutMs : d.timeoutMs,
|
||||
};
|
||||
}
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
import { type Result, err, ok } from "@uncaged/nerve-core";
|
||||
import { toJSONSchema } from "zod";
|
||||
|
||||
import type { LlmMessage, ReActTool } from "../role-types.js";
|
||||
import type { LlmProvider } from "./llm-extract.js";
|
||||
import type { LlmMessage, ReActTool } from "./role-types.js";
|
||||
|
||||
type OpenAiMessage =
|
||||
| { role: "system" | "user" | "assistant"; content: string }
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { StartStep } from "@uncaged/nerve-core";
|
||||
|
||||
/** Returns the thread-level dry-run flag from the workflow start frame. */
|
||||
export function isDryRun(start: StartStep): boolean {
|
||||
return start.meta.dryRun;
|
||||
}
|
||||
Reference in New Issue
Block a user