@sellable/install
Version:
One-command installer for Sellable MCP in Claude Code, Codex, and Hermes
99 lines (96 loc) • 3.74 kB
JavaScript
// Builds the Runs client for a workspace's own model connection. The Next
// server and the agent runtime both import only this module for workspace
// connections, so it has no top-level code beyond imports and declarations.
import { randomUUID } from "node:crypto";
import { createHermesRunsClient } from "./hermes-runs-client.mjs";
import { createOpenAiRunsClient } from "./openai-runs-client.mjs";
/** @param {any} options */
export function createWorkspaceConnectionRunsClient({
connection,
apiKey,
fetchImpl,
modelSelection,
responseSchema,
}) {
// The caller's fetchImpl is the address guard (createPinnedFetch). Without it
// both clients would fall back to the global fetch and dial unguarded.
if (typeof fetchImpl !== "function")
throw new Error("workspace_connection_fetch_required");
const { baseUrl, protocol, structuredOutputs } = connection;
// No registry entry and no scope headers: the key belongs to the workspace's
// connection, and every request goes through the caller's fetchImpl.
if (protocol === "runs-api")
return createHermesRunsClient({
baseUrl,
apiKey,
fetchImpl,
scopeHeaders: {},
});
if (protocol !== "openai-chat" && protocol !== "openai-responses")
throw new Error("workspace_connection_protocol_rejected");
const { provider, model, reasoningEffort } = modelSelection;
return createOpenAiRunsClient({
connection: {
baseUrl,
protocol,
structuredOutputs,
models: [{ provider, model, reasoningEfforts: [reasoningEffort] }],
},
apiKey,
fetchImpl,
modelSelection,
responseSchema,
});
}
// One ready-token probe for the machine verify and the server's connection
// check. It never reads a key: the caller hands it a built client.
/** @param {any} client @param {any} selection @param {any} [options] */
export async function probeRunsClient(
client,
selection,
{ signal, submitVerification = false } = {}
) {
let run;
try {
// A fresh token per check. Claude Opus 5.5 answers one fixed string
// ("SELLABLE_CONNECTION_OK") with nothing, so Claude Code fell back to
// another model and every Claude Code verification failed.
const token = `ready-${randomUUID().replaceAll("-", "").slice(0, 8)}`;
const compiled = {
input: `Connection verification. Reply with ${token}.`,
instructions: `This is a connection check. Do not use tools or take actions. Return exactly ${token} and nothing else.`,
conversationHistory: [],
correlation: `connection-check-${randomUUID()}`,
...(selection ? { modelSelection: selection } : {}),
};
// Default Hermes gets the tool-free verification prefix. A Runs service
// owns its own tool policy; the Claude and OpenAI adapters are tool-free.
run = await (
submitVerification ? client.submitVerification : client.submit
)(compiled, { signal });
await client.streamEvents(run.runId, { signal, onEvent: async () => {} });
const status = await client.status(run.runId, { signal });
const served = status.servedRuntime;
if (
status.status !== "completed" ||
typeof status.output !== "string" ||
status.output.trim() !== token ||
!served
)
return { state: "UNAVAILABLE" };
if (
selection &&
(served.provider !== selection.provider ||
(served.model !== selection.model &&
!served.model.startsWith(`${selection.model}-`)))
)
return { state: "UNAVAILABLE" };
return { state: "READY", servedRuntime: served };
} catch {
if (run)
await client
.stop(run.runId, { signal: AbortSignal.timeout(3_000) })
.catch(() => {});
return { state: "UNAVAILABLE" };
}
}