raysurfer
v1.4.4
Published
AI maintained skills for vertical agents
Maintainers
Readme
RaySurfer TypeScript SDK
AI Maintained Skills for Vertical Agents. Re-use verified code from prior runs rather than serial tool calls or generating code per execution.
Installation
npm install raysurferSetup
Set your API key:
export RAYSURFER_API_KEY=your_api_key_hereGet your key from the dashboard.
Low-Level API
For custom integrations, use the RaySurfer client directly with
any LLM provider.
Complete Example
import { RaySurfer } from "raysurfer";
const client = new RaySurfer({ apiKey: "your_api_key" });
const task = "Fetch GitHub trending repos";
// 1. Search for cached code matching a task
const result = await client.search({
task,
topK: 5,
minVerdictScore: 0.3,
});
for (const match of result.matches) {
console.log(`${match.codeBlock.name}: ${match.combinedScore}`);
console.log(` Source: ${match.codeBlock.source.slice(0, 80)}...`);
}
// 2. Upload a new code file after execution
await client.upload({
task,
fileWritten: {
path: "fetch_repos.ts",
content: "function fetch() { ... }",
},
succeeded: true,
executionLogs: "Fetched 10 trending repos successfully",
dependencies: { "node-fetch": "3.3.0", zod: "3.22.0" },
});
// 2b. Bulk upload prompts/logs/code for sandboxed grading
await client.uploadBulkCodeSnips(
["Build a CLI tool", "Add CSV support"],
[{ path: "cli.ts", content: "function main() { ... }" }],
[
{
path: "logs/run.log",
content: "Task completed",
encoding: "utf-8",
},
]
);
// 3. Vote on whether a cached snippet was useful
await client.voteCodeSnip({
task,
codeBlockId: result.matches[0].codeBlock.id,
codeBlockName: result.matches[0].codeBlock.name,
codeBlockDescription: result.matches[0].codeBlock.description,
succeeded: true,
});Client Options
const client = new RaySurfer({
apiKey: "your_api_key",
baseUrl: "https://api.raysurfer.com", // optional
timeout: 30000, // optional, in ms
organizationId: "org_xxx", // optional, for team namespacing
workspaceId: "ws_xxx", // optional, for enterprise namespacing
snipsDesired: "company", // optional, snippet scope
publicSnips: true, // optional, include community snippets
});Response Fields
The search() response includes:
| Field | Type | Description |
| ---------------- | --------------- | --------------------------------- |
| matches | SearchMatch[] | Matching code blocks with scoring |
| totalFound | number | Total matches found |
| cacheHit | boolean | Whether results were from cache |
Each SearchMatch contains codeBlock (with id, name,
source, description, entrypoint, language, dependencies),
combinedScore, vectorScore, verdictScore, thumbsUp,
thumbsDown, filename, and entrypoint.
Store a Code Block with Full Metadata
const result = await client.storeCodeBlock({
name: "GitHub User Fetcher",
source: "function fetchUser(username) { ... }",
entrypoint: "fetchUser",
language: "typescript",
description: "Fetches user data from GitHub API",
tags: ["github", "api", "user"],
dependencies: { "node-fetch": "3.3.0" },
});Retrieve Few-Shot Examples
const examples = await client.getFewShotExamples(
"Parse CSV files",
3
);
for (const ex of examples) {
console.log(`Task: ${ex.task}`);
console.log(`Code: ${ex.codeSnippet}`);
}Retrieve Task Patterns
const patterns = await client.getTaskPatterns({
task: "API integration",
minThumbsUp: 5,
topK: 20,
});
for (const p of patterns) {
console.log(`${p.taskPattern} -> ${p.codeBlockName}`);
}User-Provided Votes
Instead of relying on AI voting, provide your own votes:
// Single upload with your own vote (AI voting is skipped)
await client.upload({
task: "Fetch GitHub trending repos",
fileWritten: file,
succeeded: true,
userVote: 1, // 1 = thumbs up, -1 = thumbs down
});
// Bulk upload with per-file votes (AI grading is skipped)
await client.uploadBulkCodeSnips(
["Build a CLI tool", "Add CSV support"],
files,
logs,
true, // useRaysurferAiVoting (ignored when userVotes set)
{ "app.ts": 1, "utils.ts": -1 } // userVotes
);Method Reference
| Method | Description |
|--------|-------------|
| search({ task, topK?, minVerdictScore?, preferComplete?, inputSchema? }) | Search for cached code snippets |
| getCodeSnips({ task, topK?, minVerdictScore? }) | Retrieve cached code snippets by semantic search |
| retrieveBest({ task, topK?, minVerdictScore? }) | Retrieve the single best match |
| getFewShotExamples(task, k) | Retrieve few-shot examples for code generation prompting |
| getTaskPatterns({ task, minThumbsUp?, topK? }) | Retrieve proven task-to-code mappings |
| storeCodeBlock({ name, source, entrypoint, language, description, tags?, dependencies?, ... }) | Store a code block with full metadata |
| upload({ task, fileWritten, succeeded, useRaysurferAiVoting?, userVote?, executionLogs?, dependencies? }) | Store a single code file with optional dependency versions |
| uploadBulkCodeSnips(prompts, filesWritten, logFiles?, useRaysurferAiVoting?, userVotes?) | Bulk upload for grading (AI votes by default, or provide per-file votes) |
| delete(snippetId) | Delete a snippet by ID or name |
| voteCodeSnip({ task, codeBlockId, codeBlockName, codeBlockDescription, succeeded }) | Vote on snippet usefulness |
Exceptions
Both clients include built-in retry logic with exponential backoff for transient failures (429, 5xx, network errors).
| Exception | Description |
| ----------------------- | ---------------------------------------------------- |
| RaySurferError | Base exception for all Raysurfer errors |
| APIError | API returned an error response (includes statusCode) |
| AuthenticationError | API key is invalid or missing |
| CacheUnavailableError | Cache backend is unreachable |
| RateLimitError | Rate limit exceeded after retries (includes retryAfter) |
| ValidationError | Request validation failed (includes field) |
import { RaySurfer, RateLimitError } from "raysurfer";
const client = new RaySurfer({ apiKey: "your_api_key" });
try {
const result = await client.getCodeSnips({
task: "Fetch GitHub repos",
});
} catch (e) {
if (e instanceof RateLimitError) {
console.log(`Rate limited after retries: ${e.message}`);
if (e.retryAfter) {
console.log(`Try again in ${e.retryAfter}ms`);
}
}
}Claude Agent SDK Drop-in
Swap your import — everything else stays the same:
// Before
import { query } from "@anthropic-ai/claude-agent-sdk";
// After
import { query } from "raysurfer";
for await (const message of query({
prompt: "Fetch data from GitHub API",
options: {
model: "claude-opus-4-6",
systemPrompt: "You are a helpful assistant.",
},
})) {
console.log(message);
}All Claude SDK types are re-exported from raysurfer, so you don't
need a separate import:
import {
query,
type Options,
type SDKMessage,
type Query,
} from "raysurfer";Class-based API
import { ClaudeSDKClient } from "raysurfer";
const client = new ClaudeSDKClient({
model: "claude-opus-4-6",
systemPrompt: "You are a helpful assistant.",
});
for await (const msg of client.query("Fetch data from GitHub API")) {
console.log(msg);
}System Prompt Preset
Use the Claude Code preset system prompt with appended instructions:
for await (const message of query({
prompt: "Refactor the auth module",
options: {
systemPrompt: {
type: "preset",
preset: "claude_code",
append: "Always explain your reasoning.",
},
},
})) {
console.log(message);
}Query Control Methods
The query() function returns a Query object with full control
methods:
const q = query({ prompt: "Build a REST API" });
await q.interrupt();
await q.setPermissionMode("acceptEdits");
await q.setModel("claude-sonnet-4-5-20250929");
await q.setMaxThinkingTokens(4096);
const models = await q.supportedModels();
const info = await q.accountInfo();
q.close();Without Caching
If RAYSURFER_API_KEY is not set, behaves exactly like the original
SDK — no caching, just a pass-through wrapper.
Snippet Retrieval Scope
Control which cached snippets are retrieved:
import { ClaudeSDKClient } from "raysurfer";
// Include company-level snippets (Team/Enterprise)
const client = new ClaudeSDKClient({
snipsDesired: "company",
});
// Enterprise: client-specific snippets only
const enterpriseClient = new ClaudeSDKClient({
snipsDesired: "client",
});| Configuration | Required Tier |
|------------------------------|---------------------|
| Default (public only) | FREE |
| snipsDesired: "company" | TEAM or ENTERPRISE |
| snipsDesired: "client" | ENTERPRISE only |
Public Snippets
Include community public snippets (crawled from GitHub) in retrieval results alongside your private snippets:
// High-level
const client = new ClaudeSDKClient({ publicSnips: true });
// Low-level
const rs = new RaySurfer({ apiKey: "...", publicSnips: true });Agent-Accessible Functions
Mark functions as callable by agents with agentAccessible().
Name and parameters are auto-inferred from the function when
not provided:
import { agentAccessible, toAnthropicTool } from "raysurfer";
function fetchWeather(city: string, units: string) {
return { temp: 72, city, units };
}
// Auto-inferred: name="fetchWeather", parameters={city, units}
const tool = agentAccessible(fetchWeather);
// Or provide explicit metadata
const toolExplicit = agentAccessible(fetchWeather, {
name: "get_weather",
description: "Fetch current weather for a city",
inputSchema: { city: "string", units: "string" },
});Convert to Anthropic Tool
Use toAnthropicTool() to get an Anthropic-compatible tool
definition:
const anthropicTool = toAnthropicTool(toolExplicit);
// { name: "get_weather",
// description: "Fetch current weather for a city",
// input_schema: { city: "string", units: "string" } }Programmatic Tool Calling
Register local tools, then either:
- pass in
userCode(primary mode), or - generate code inside the sandbox with your own provider key + prompt (optional mode).
import { RaySurfer } from "raysurfer";
const rs = new RaySurfer({ apiKey: "your_api_key" });
rs.tool("add", "Add two numbers together", { a: "integer", b: "integer" },
async (args) => String(Number(args.a) + Number(args.b))
);
rs.tool("multiply", "Multiply two numbers together", { a: "integer", b: "integer" },
async (args) => String(Number(args.a) * Number(args.b))
);
const userCode = `
intermediate = add(5, 3)
final = multiply(intermediate, 2)
print(final)
`;
const result = await rs.execute(
"Add 5 and 3, then multiply the result by 2",
{ userCode }
);
console.log(result.result); // "16"
console.log(result.toolCalls); // [{toolName: 'add', ...}, ...]
console.log(result.cacheHit); // false (reserved field)How It Works
- SDK connects a WebSocket to the server for tool call routing
- Your app sends either
userCode(primary mode) orcodegeninputs (optional mode) to/api/execute/run - Code runs in a sandboxed environment — tool calls are routed back to your local functions via WebSocket
- Results are returned with full tool call history
Execute Options
const result = await rs.execute("Your task description", {
userCode: "print(add(1, 2))", // Primary mode
timeout: 300000, // Max time in ms (default 300000)
});
// Optional mode: generate code in sandbox using your own key + prompt
const sandboxCodegenResult = await rs.execute("Your task description", {
codegen: {
provider: "anthropic",
apiKey: "your_anthropic_key",
model: "claude-opus-4-6",
prompt: "Write Python code that uses add(a, b) and prints the result for 2 + 3.",
},
timeout: 300000,
});ExecuteResult Fields
| Field | Type | Description |
| ------------- | ------------------ | -------------------------------- |
| executionId | string | Unique execution identifier |
| result | string \| null | Stdout output from the script |
| exitCode | number | Process exit code (0 = success) |
| durationMs | number | Total execution time |
| cacheHit | boolean | Reserved field (always false) |
| error | string \| null | Error message if exitCode != 0 |
| toolCalls | ToolCallRecord[] | All tool calls made |
License
MIT
