@sellable/install
Version:
One-command installer for Sellable MCP in Claude Code, Codex, and Hermes
123 lines (119 loc) • 4.87 kB
JavaScript
import { createHash } from "node:crypto";
import { compileEndpointModelSelection } from "./endpoint-model-selection.mjs";
import { createHermesRunsClient } from "./hermes-runs-client.mjs";
import { createPinnedFetch } from "./public-endpoint-guard.mjs";
import { createWorkspaceConnectionRunsClient } from "./workspace-connection-client.mjs";
const record = (value) =>
value && typeof value === "object" && !Array.isArray(value);
const stable = (value) =>
Array.isArray(value)
? `[${value.map(stable).join(",")}]`
: record(value)
? `{${Object.keys(value)
.sort()
.map((key) => `${JSON.stringify(key)}:${stable(value[key])}`)
.join(",")}}`
: JSON.stringify(value);
// Exactly the members the endpoint-connection-key verb returns. The key follows
// the saved key's rule (16-1024 characters), never the key-file rule; the
// remote tool's values are checked where the create body is built.
const connectionRelease = (value) =>
record(value) &&
Object.keys(value).sort().join(",") ===
"apiKey,keyGeneration,protocol,structuredOutputs,tools" &&
typeof value.apiKey === "string" &&
value.apiKey.length >= 16 &&
value.apiKey.length <= 1024 &&
!/[\s\x00-\x1f\x7f]/.test(value.apiKey) &&
Number.isSafeInteger(value.keyGeneration) &&
["runs-api", "openai-chat", "openai-responses"].includes(value.protocol) &&
typeof value.structuredOutputs === "boolean" &&
(value.tools === null ||
(value.protocol === "runs-api" &&
record(value.tools) &&
Object.keys(value.tools).sort().join(",") ===
"allowedTools,authorization,expiresAt,serverUrl" &&
typeof value.tools.serverUrl === "string" &&
typeof value.tools.authorization === "string" &&
Array.isArray(value.tools.allowedTools) &&
typeof value.tools.expiresAt === "string"));
// Adds the opener output contract (and a granted remote tool) to every submit.
function customRunsClient(client, claim, remoteTools) {
return Object.freeze({
...client,
submit(compiled, options) {
const request = remoteTools ? { ...compiled, remoteTools } : compiled;
// Public semantic endpoints have a published schema but no product tuple.
// Preserve that existing contract when calling a custom Runs service.
const schema = claim.revision?.outputSchema;
if (compiled.outputContract || !schema?.properties?.proposal)
return client.submit(request, options);
return client.submit(
{
...request,
outputContract: {
schemaVersion: "sellable-opener-output-contract/v1",
schema,
nativeProposalSchemaDigest: createHash("sha256")
.update(stable(schema.properties.proposal))
.digest("hex"),
},
},
options
);
},
});
}
/** @param {any} options */
export async function createConfiguredRunsClient({
apiKeyFile,
claim,
clientFactory = createHermesRunsClient,
fetchConnectionKey,
chainStep = 0,
createFetch = createPinnedFetch,
}) {
const selection = claim?.run?.modelSelection;
// A workspace connection's key is fetched for this attempt and lives only in
// these closures, and every request goes through the address guard. URL and
// model come from the signed claim, never from the key response.
if (selection?.modelConnectionId) {
const step = compileEndpointModelSelection(selection);
if (!step || step === "invalid") throw new Error("runs_api_model_rejected");
let release = null;
try {
release = await fetchConnectionKey(step.modelConnectionId, chainStep);
} catch {
// Every refusal is one fixed code: no server reason, no response field.
}
if (!connectionRelease(release))
throw new Error("runs_api_connection_unavailable");
const { apiKey, protocol, structuredOutputs, tools } = release;
const client = createWorkspaceConnectionRunsClient({
connection: { baseUrl: step.runsApiBaseUrl, protocol, structuredOutputs },
apiKey,
// An OpenAI-protocol provider sends its headers only when it is done, so
// the adapter's 750 s run signal bounds that call, not the guard.
fetchImpl:
protocol === "runs-api"
? createFetch()
: createFetch({ deadlineMs: 760_000 }),
modelSelection: step,
responseSchema: claim.revision?.outputSchema,
});
return customRunsClient(
client,
claim,
tools && {
serverUrl: tools.serverUrl,
authorization: tools.authorization,
allowedTools: tools.allowedTools,
}
);
}
// A custom URL is served only through a workspace connection: no other
// client, credential or socket is opened for it.
if (selection?.runsApiBaseUrl)
throw new Error("runs_api_connection_unavailable");
return clientFactory({ apiKeyFile });
}