mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-09-10 19:26:00 +00:00
feat(agentswarm): M1+M3 — embeddable swarm runtime wrapping deepagents
@logicsrc/agentswarm: a self-hosted, framework-agnostic multi-agent runtime that each app mounts on its own route (e.g. tronbrowser.dev/swarm). M1 — core: - createSwarmHandler(): Web (Request)=>Response handler, CORS + validation - SwarmRunner: injectable engine interface - createDeepAgentRunner(): deepagents (createDeepAgent) adapter; deepagents + @langchain/langgraph are optional peers loaded via dynamic import so the core builds/tests with zero heavy deps - SwarmError + onRequest gate: seam for x402 metering / auth (402/403) M3 — rubric self-check (deepagents RubricMiddleware is Python-only, so ported at the runner layer): - createRubricRunner(): grades output via an injectable judge and revises until it passes or maxIterations; passes through untouched when no rubric - createLLMJudge(): cheap-model grader (lazy langchain initChatModel) 13/13 vitest pass; tsc clean; examples/server.mjs demo verified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ffe37a01ce
commit
018a583a00
13 changed files with 805 additions and 1 deletions
87
packages/agentswarm/src/runner.ts
Normal file
87
packages/agentswarm/src/runner.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import type { SwarmMessage, SwarmRunInput, SwarmRunResult, SwarmRunner } from "./types.js";
|
||||
|
||||
export interface DeepAgentRunnerOptions {
|
||||
/** Provider-prefixed model id, e.g. "anthropic:claude-sonnet-4-6". */
|
||||
model: string;
|
||||
/** System instructions for the lead agent. */
|
||||
instructions?: string;
|
||||
/**
|
||||
* LangGraph checkpointer for thread persistence. Defaults to an in-memory
|
||||
* `MemorySaver`. Swap for a Turso/SQLite-backed saver to persist threads
|
||||
* across restarts (the tronbrowser BYO-SQLite path).
|
||||
*/
|
||||
checkpointer?: unknown;
|
||||
/** Extra params forwarded to deepagents `createDeepAgent` (subagents, middleware, …). */
|
||||
agentParams?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indirection so TypeScript does not statically resolve the optional peers
|
||||
* (`deepagents`, `@langchain/langgraph`). They are only required by hosts that
|
||||
* use the real runner; the core package and its tests build without them.
|
||||
*/
|
||||
async function loadModule(specifier: string): Promise<any> {
|
||||
return import(specifier);
|
||||
}
|
||||
|
||||
function newThreadId(): string {
|
||||
return `thread_${crypto.randomUUID()}`;
|
||||
}
|
||||
|
||||
/** Map a LangChain message object to a plain {@link SwarmMessage}. */
|
||||
function toSwarmMessage(message: any): SwarmMessage {
|
||||
const type =
|
||||
typeof message?.getType === "function" ? message.getType() : (message?.role ?? message?.type);
|
||||
const role: SwarmMessage["role"] =
|
||||
type === "human" || type === "user" ? "user" : type === "system" ? "system" : "assistant";
|
||||
const content =
|
||||
typeof message?.content === "string" ? message.content : JSON.stringify(message?.content ?? "");
|
||||
return { role, content };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SwarmRunner} backed by deepagents (`createDeepAgent`).
|
||||
*
|
||||
* Requires the optional peers `deepagents` and `@langchain/langgraph`, plus a
|
||||
* model provider (e.g. `@langchain/anthropic`), to be installed by the host app:
|
||||
* `npm i deepagents @langchain/langgraph @langchain/anthropic`.
|
||||
*/
|
||||
export async function createDeepAgentRunner(options: DeepAgentRunnerOptions): Promise<SwarmRunner> {
|
||||
let deepagents: any;
|
||||
try {
|
||||
deepagents = await loadModule("deepagents");
|
||||
} catch {
|
||||
throw new Error(
|
||||
"createDeepAgentRunner requires the 'deepagents' package. Install it in the host app: " +
|
||||
"npm i deepagents @langchain/langgraph @langchain/anthropic"
|
||||
);
|
||||
}
|
||||
|
||||
let checkpointer = options.checkpointer;
|
||||
if (!checkpointer) {
|
||||
const langgraph = await loadModule("@langchain/langgraph");
|
||||
checkpointer = new langgraph.MemorySaver();
|
||||
}
|
||||
|
||||
const agent = deepagents.createDeepAgent({
|
||||
model: options.model,
|
||||
...(options.instructions ? { instructions: options.instructions } : {}),
|
||||
checkpointer,
|
||||
...options.agentParams
|
||||
});
|
||||
|
||||
return {
|
||||
async run(input: SwarmRunInput): Promise<SwarmRunResult> {
|
||||
const threadId = input.threadId ?? newThreadId();
|
||||
const state: Record<string, unknown> = { messages: input.messages };
|
||||
if (input.rubric) state.rubric = input.rubric;
|
||||
|
||||
const result = await agent.invoke(state, { configurable: { thread_id: threadId } });
|
||||
const messages: SwarmMessage[] = Array.isArray(result?.messages)
|
||||
? result.messages.map(toSwarmMessage)
|
||||
: [];
|
||||
const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant");
|
||||
return { threadId, messages, output: lastAssistant?.content ?? "" };
|
||||
}
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue