Skip to content

Custom proxy contract

When you call configureClient({ proxyUrl }), the SDK serialises every function invocation to JSON and POSTs it to your endpoint. Your endpoint returns a JSON envelope holding the LLM result.

POST /api/neuro HTTP/1.1
Content-Type: application/json
interface NeuroProxyRequest {
/** e.g. "Array.prototype.map", "Math.random", "globalThis.parseInt". */
functionId: string;
/** Natural-language prompt as supplied by the application. */
prompt: string;
/**
* Receiver / `this` value, JSON-serialised by the SDK with safe handling
* of Date, Map, Set, RegExp, BigInt, TypedArrays, and circular refs.
* `null` for static methods.
*/
instanceData: string | null;
/**
* Named arguments map. Keys mirror the original JavaScript built-in's
* parameter names (e.g. `callbackfn`, `searchString`, `fromIndex`).
* Variadic items live under their declared rest-parameter name
* (e.g. `items`, `values`, `codes`).
*/
args: Record<string, unknown>;
/** Original parameter signature for the LLM. */
signatureHint: { name: string; type: string }[];
/**
* Frozen, generated system prompt for this method. Forwarding it
* verbatim is recommended. `neuro-ts` ships the same string in
* `prompts.json` so consumers can audit.
*/
systemPrompt: string;
/** Model the SDK requested (already merged with NeuroClient defaults). */
model: string;
}

instanceData is the receiver value (this), serialised by the SDK before transport. The serialiser handles Date, Map, Set, RegExp, BigInt, every TypedArray, and circular references; the result is always a string the model can read.

// neuro.array.map({ array: [1, 2, 3], callbackfn: (n) => n * 2, prompt: '...' })
// functionId -> "Array.prototype.map"
// instanceData -> "[1,2,3]"
// neuro.string.toUpperCase({ string: 'hi mum', prompt: '...' })
// functionId -> "String.prototype.toUpperCase"
// instanceData -> "\"hi mum\""
// neuro.map.get({ map: new Map([['a', 1]]), key: 'a', prompt: '...' })
// functionId -> "Map.prototype.get"
// instanceData -> "{\"__type\":\"Map\",\"entries\":[[\"a\",1]]}"
// neuro.math.random({ prompt: '...' })
// functionId -> "Math.random"
// instanceData -> null (static methods have no receiver)

For prototype methods the receiver is whatever the caller passed under the receiver-named key (array, string, map, etc.). For static methods (Math.*, JSON.*, Promise.*) and globals (parseInt, encodeURI) the value is always null.

The SDK accepts three shapes (in order of preference):

// 1. Wrapped result. Recommended.
{ "result": <any JSON value> }
// 2. Wrapped text (will be parsed as JSON; falls back to raw string).
{ "text": "[1, 4, 9]" }
// 3. Bare value.
[1, 4, 9]

Non-2xx responses surface as NeuroClientError on the caller side, with the response body included in error.message.

import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
export async function POST(req) {
const body = await req.json();
const argLines = Object.entries(body.args ?? {})
.map(([k, v]) => `- ${k} = ${JSON.stringify(v)}`)
.join('\n');
const completion = await openai.chat.completions.create({
model: body.model,
temperature: 0.2,
messages: [
{ role: 'system', content: body.systemPrompt },
{
role: 'user',
content:
`## User intent\n${body.prompt}\n\n` +
`## Function\n\`${body.functionId}\`\n\n` +
`## Receiver / \`this\` value\n${body.instanceData ?? 'null'}\n\n` +
`## Named arguments\n${argLines || '(none)'}`,
},
],
});
const text = completion.choices[0]?.message?.content ?? '';
let result = text;
try {
result = JSON.parse(text);
} catch {
/* leave raw */
}
return Response.json({ result });
}

neuro-ts/proxy ships a Web-standard (req: Request) => Response handler that implements this contract:

import { createNeuroProxy } from 'neuro-ts/proxy';
export default {
fetch: createNeuroProxy({
apiKey: process.env.OPENAI_API_KEY,
defaultModel: 'gpt-4o',
allowedFunctionIds: ['Array.prototype.map', 'Math.random'],
}),
};

allowedFunctionIds — required for production

Section titled “allowedFunctionIds — required for production”

allowedFunctionIds is an explicit allowlist of the functionId values your proxy will serve. If omitted, every function id is accepted.

That means a caller could send functionId: "eval" or any other string and your proxy would forward it to the LLM with your API key. In production, always set an explicit allowlist:

createNeuroProxy({
apiKey: process.env.OPENAI_API_KEY,
allowedFunctionIds: [
'Array.prototype.map',
'Array.prototype.filter',
'JSON.parse',
'Math.random',
],
});

Requests for unlisted function ids receive a 403 response. The full list of valid functionId values is the set of keys in prompts.json, available at runtime via:

import prompts from 'neuro-ts/prompts';
const valid = Object.values(prompts).map((e) => e.functionId);

Because every request includes prompt, instanceData, and the named args, your proxy is the right layer to:

  1. Authenticate the caller (cookie session, OAuth, API key).
  2. Cap request size. The SDK already bounds instanceData at 8 KiB; the prompt is not bounded.
  3. Log functionId plus caller for billing and abuse review.
  4. Apply per-user rate limits.