/** * The MiniMaxAdapter — Paperclip's adapter contract surface. * * The community OpenRouter adapter (paperclip-adapter-openrouter) is * the reference implementation we pattern after. It calls out these * requirements: * * - `supportsLocalAgentJwt: true` so the heartbeat dispatcher mints a * per-run JWT and injects it via createLocalAgentJwt() * - Pure HTTP: implements the entire tool loop in TypeScript, no CLI * subprocess * - Calls Paperclip's REST API directly with that JWT for the 8 * built-in tools (get_issue, update_issue_status, add_comment, * list_comments, create_sub_issue, list_issues, hire_agent, * request_approval) * - Routes approval-gated tools through /api/companies/:id/approvals * - Implements sessionCodec, detectModel, listSkills, syncSkills * - Repeat-call loop break * - Emits structured transcript events for the live Working panel */ import fs from "node:fs"; import path from "node:path"; import { configSchema, loadConfig } from "./config.js"; import { MiniMaxClient } from "./client.js"; import { execute } from "./execute.js"; import { loadSkills } from "./skills.js"; import { sessionCodec } from "./session.js"; import type { AdapterConfig, AdapterResult, AgentContext, IssueRef, RunContext, } from "./types.js"; export interface AdapterMetadata { name: string; version: string; provider: string; supportsLocalAgentJwt: boolean; configSchema: typeof configSchema; } export const adapterMetadata: AdapterMetadata = { name: "minimax", version: "0.1.0", provider: "minimax", supportsLocalAgentJwt: true, configSchema, }; export const MiniMaxAdapter = { metadata: adapterMetadata, /** * List the model IDs MiniMax currently advertises. Calls * `GET /v1/models` on the configured base URL. */ async detectModel(config: AdapterConfig): Promise { const client = new MiniMaxClient(config); const result = await client.validate(); return result.ok ? result.models : [config.model]; }, /** * List skill files for the agent's workspace. * Skills are read-only at call time; sync is a no-op for this adapter * (the workspace owner commits skills, the adapter just loads them). */ async listSkills(workspacePath: string | undefined, skillsDir?: string): Promise { return loadSkills(workspacePath, skillsDir).map((s) => s.path); }, /** No-op sync; skills are committed to disk by the workspace owner. */ async syncSkills(workspacePath: string | undefined, _skillsDir?: string): Promise<{ added: string[]; updated: string[]; removed: string[] }> { // We don't manage skill files — we just read them. The workspace // owner is responsible for adding/updating. Return zero-delta to // satisfy the contract. void workspacePath; return { added: [], updated: [], removed: [] }; }, /** Re-export so Paperclip's adapter runner can call into us. */ sessionCodec, /** * Main heartbeat entry point. Paperclip's `heartbeat.ts` calls this * with the agent record, the run context (with the pre-minted JWT * already in our env), and the issue. */ async run( args: { configOverrides?: Partial; agent: AgentContext; run: RunContext; issue: IssueRef; parentChain?: IssueRef[]; resumeSessionState?: Record; }, ): Promise { const config = loadConfig(args.configOverrides ?? {}); const resumeSession = args.resumeSessionState ? (sessionCodec.deserialize(args.resumeSessionState) as unknown as import("./session.js").SessionState | null) ?? undefined : undefined; return execute({ config, agent: args.agent, run: args.run, issue: args.issue, parentChain: args.parentChain, resumeSessionState: resumeSession, }); }, }; /** Sensible default instance for direct import. */ export const minimaxAdapter = MiniMaxAdapter;