{"_id":"@abhijeetkadam/redai","_rev":"2-d9778a0f4128e6f47ca73e1cd5131a1f","name":"@abhijeetkadam/redai","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@abhijeetkadam/redai","version":"0.1.0","keywords":["agent","agents","ai","llm","sdk","openai","tool-calling","guardrails","streaming","tracing"],"author":{"name":"Abhijeet Kadam"},"license":"MIT","_id":"@abhijeetkadam/redai@0.1.0","maintainers":[{"name":"abhijeetkadam","email":"kadamabhi1881@gmail.com"}],"dist":{"shasum":"9624e53c7882cc13b58f15ea94a2b05590971f25","tarball":"https://registry.npmjs.org/@abhijeetkadam/redai/-/redai-0.1.0.tgz","fileCount":9,"integrity":"sha512-MpYKHwYJsSTIXfPTvG6XctyehiqRovHEhg4TeEHEvbAbNU6YFFfV2s2FLArojXevOpMIkt7BMpjSIB5Lx9PY6g==","signatures":[{"sig":"MEQCIDK0ifJe1oPkXfHH0HuyTCxFrZMKba6yy/C7PxEmbhWrAiBuPDASs10Y00vrJTjZKcsCtxGyUiItOYt14z3XoS3X8w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":220862},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"2cec2dad02831c5887bba64793d4226cd374deba","scripts":{"dev":"tsup --watch","test":"echo \"Error: no test specified\" && exit 1","build":"tsup","typecheck":"tsc --noEmit","example:basic":"tsx examples/basic.ts","example:tracing":"tsx examples/tracing.ts","example:handoffs":"tsx examples/handoffs.ts","example:sessions":"tsx examples/sessions.ts","example:streaming":"tsx examples/streaming.ts","example:guardrails":"tsx examples/guardrails.ts","example:structured-output":"tsx examples/structured-output.ts"},"_npmUser":{"name":"abhijeetkadam","email":"kadamabhi1881@gmail.com"},"_npmVersion":"11.12.1","description":"A from-scratch TypeScript agent SDK - agent loop, tools, guardrails, sessions, handoffs, structured output, streaming, and tracing. No agent framework dependency.","directories":{},"_nodeVersion":"25.9.0","dependencies":{"zod":"^3.24.1","openai":"^7.1.0","zod-to-json-schema":"^3.24.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.2","tsup":"^8.3.5","typescript":"^5.7.3","@types/node":"^26.1.2"},"_npmOperationalInternal":{"tmp":"tmp/redai_0.1.0_1785749958479_0.7080845682738461","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@abhijeetkadam/redai","version":"0.2.0","publishConfig":{"access":"public"},"description":"A from-scratch TypeScript agent SDK - agent loop, tools, guardrails, sessions, handoffs, structured output, streaming, and tracing. No agent framework dependency.","license":"MIT","author":{"name":"Abhijeet Kadam"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"keywords":["agent","agents","ai","llm","sdk","openai","tool-calling","guardrails","streaming","tracing"],"scripts":{"build":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","example:basic":"tsx examples/basic.ts","example:sessions":"tsx examples/sessions.ts","example:structured-output":"tsx examples/structured-output.ts","example:guardrails":"tsx examples/guardrails.ts","example:handoffs":"tsx examples/handoffs.ts","example:streaming":"tsx examples/streaming.ts","example:tracing":"tsx examples/tracing.ts","example:providers":"tsx examples/providers.ts","test":"echo \"Error: no test specified\" && exit 1"},"devDependencies":{"@anthropic-ai/sdk":"^0.115.0","@google/genai":"^2.15.0","@types/node":"^26.1.2","openai":"^7.1.0","tsup":"^8.3.5","tsx":"^4.19.2","typescript":"^5.7.3"},"dependencies":{"zod":"^3.24.1","zod-to-json-schema":"^3.24.1"},"peerDependencies":{"@anthropic-ai/sdk":"^0.115.0","@google/genai":"^2.15.0","openai":"^7.1.0"},"peerDependenciesMeta":{"@anthropic-ai/sdk":{"optional":true},"@google/genai":{"optional":true},"openai":{"optional":true}},"gitHead":"2cec2dad02831c5887bba64793d4226cd374deba","_id":"@abhijeetkadam/redai@0.2.0","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-cHKQBAX/dtidMJEhjNiZNL666FgMge+EG7QiJV5a/oURSDnBX7Fyt+/wMX1AVteM3y1h7mWAXDjUhMF0B0WczQ==","shasum":"2042985c4de3b13e1ce3871ac22da662af3c736f","tarball":"https://registry.npmjs.org/@abhijeetkadam/redai/-/redai-0.2.0.tgz","fileCount":9,"unpackedSize":307408,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHL1mycxMxYUj0fgoVwKV24aAoW8f0hxDqAqIi9F+BEiAiBlcnBqKo4KdB3+Iqw2cg3Qvi88KB+OV9bPNBiz7RGWpg=="}]},"_npmUser":{"name":"abhijeetkadam","email":"kadamabhi1881@gmail.com"},"directories":{},"maintainers":[{"name":"abhijeetkadam","email":"kadamabhi1881@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/redai_0.2.0_1785751014251_0.005758139852356292"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-03T09:39:18.345Z","modified":"2026-08-03T09:56:54.569Z","0.1.0":"2026-08-03T09:39:18.628Z","0.2.0":"2026-08-03T09:56:54.403Z"},"author":{"name":"Abhijeet Kadam"},"license":"MIT","keywords":["agent","agents","ai","llm","sdk","openai","tool-calling","guardrails","streaming","tracing"],"description":"A from-scratch TypeScript agent SDK - agent loop, tools, guardrails, sessions, handoffs, structured output, streaming, and tracing. No agent framework dependency.","maintainers":[{"name":"abhijeetkadam","email":"kadamabhi1881@gmail.com"}],"readme":"# redai\n\nA from-scratch TypeScript agent SDK. No agent framework underneath — the loop, tool dispatch,\nguardrails, handoffs, streaming, and tracing are all hand-written here. `zod` and\n`zod-to-json-schema` are the only required dependencies; model provider SDKs (`openai`,\n`@anthropic-ai/sdk`, `@google/genai`) are optional peer dependencies - install whichever one(s)\nyou actually use.\n\n## Install\n\n```bash\nnpm install @abhijeetkadam/redai openai\n# and/or: npm install @anthropic-ai/sdk\n# and/or: npm install @google/genai\n```\n\n## Quick start\n\n```ts\nimport { z } from 'zod'\nimport { Agent, openai, run, tool } from '@abhijeetkadam/redai'\n\nconst getWeather = tool({\n    name: 'getWeather',\n    description: 'Fetches current weather conditions for a city',\n    inputSchema: z.object({ city: z.string() }),\n    async execute({ city }) {\n        const res = await fetch(`https://wttr.in/${encodeURIComponent(city)}?format=%C+%t`)\n        return { city, conditions: (await res.text()).trim() }\n    },\n})\n\nconst agent = new Agent({\n    name: 'WeatherAgent',\n    instructions: 'You are a helpful weather assistant.',\n    model: openai({ model: 'gpt-4o' }), // reads OPENAI_API_KEY from the environment\n    tools: [getWeather],\n})\n\nconst result = await run(agent, 'What is the weather like in Goa right now?')\nconsole.log(result.ok ? result.output : result.error.message)\n```\n\nEvery capability below has a runnable, verified example in [`examples/`](examples). Most use\n`mockProvider` so they run with no API key and no network dependency (`basic.ts` is the\nexception - it calls a real weather API to prove a real async tool works end to end).\n\n## Core concepts\n\n**Agent** - static, immutable config: name, instructions, model, tools, optional output schema,\nguardrails, handoffs. Holds no run state.\n\n```ts\nconst agent = new Agent({\n    name: 'MyAgent',\n    instructions: 'You are a helpful assistant.',\n    model: openai({ model: 'gpt-4o' }),\n    tools: [myTool],\n})\n```\n\n**Tool** - a Zod-validated input schema plus an `execute` function. Bad JSON, failed validation,\nor a thrown error all become a message back to the model instead of crashing the run.\n\n```ts\nconst myTool = tool({\n    name: 'myTool',\n    description: '...',\n    inputSchema: z.object({ x: z.number() }),\n    async execute({ x }) {\n        return { doubled: x * 2 }\n    },\n})\n```\n\n**Runner** (`run` / `stream`) - the agent loop: send context to the model, detect tool calls,\nexecute them, feed results back, repeat until a final answer or `agent.maxTurns` is hit\n(returned as a clean failure, never a throw).\n\n```ts\nconst result = await run(agent, 'do something')\n// result.ok ? result.output : result.error\n```\n\n**Sessions** - multi-turn conversations, persisted separately from the agent's config. Bring your\nown `SessionStore`, or use the built-in ones:\n\n```ts\nimport { InMemorySessionStore, Session, FileSessionStore } from '@abhijeetkadam/redai'\n\nconst session = new Session('user-123', new InMemorySessionStore())\n// or: new Session('user-123', new FileSessionStore({ directory: './sessions' }))\n\nawait run(agent, 'hi', { session })\nawait run(agent, 'what did I just say?', { session }) // sees the prior turn\n```\n\n**Structured output** - give the agent a Zod `outputSchema` and get back a typed, validated\nresult. On a validation failure the model is re-prompted with the specific errors up to\n`maxOutputRepairAttempts` (default 2) before the run fails cleanly.\n\n```ts\nconst agent = new Agent({\n    ...,\n    outputSchema: z.object({ answer: z.string(), confidence: z.number() }),\n})\nconst result = await run(agent, '...')\nif (result.ok) result.output.confidence // fully typed\n```\n\n**Guardrails** - input/output/tool hooks that can reject or modify content.\n\n```ts\nimport { inputGuardrail, outputGuardrail, toolGuardrail } from '@abhijeetkadam/redai'\n\nconst agent = new Agent({\n    ...,\n    guardrails: {\n        input: [inputGuardrail('noBlank', (text) => (text.trim() ? { pass: true } : { pass: false, reason: 'blank input' }))],\n        output: [outputGuardrail('redactCards', (text) => ({ pass: true, modifiedContent: text.replace(/\\d{16}/, '[REDACTED]') }))],\n        tool: [\n            toolGuardrail('safePaths', (toolName, input) => {\n                const { path } = input as { path: string }\n                return path.startsWith('/safe/') ? { pass: true } : { pass: false, reason: 'path not allowed' }\n            }),\n        ],\n    },\n})\n```\n\n**Handoffs** - one agent can delegate to another mid-run. The full transcript carries over; only\nthe system prompt swaps to the new agent's instructions. Loop-safe by construction: an agent\nhanding off to itself is blocked instantly, and a configurable `maxHandoffs` (default 10) caps\nlonger chains.\n\n```ts\nconst billingAgent = new Agent({ name: 'BillingAgent', ... })\nconst triageAgent = new Agent({ name: 'TriageAgent', ..., handoffs: [billingAgent] })\n\nconst result = await run(triageAgent, 'What did I pay on my last invoice?')\nresult.handoffs // [{ from: 'TriageAgent', to: 'BillingAgent', reason: '...', turn: 0 }]\n```\n\n**Streaming & events** - `stream()` is an async generator yielding events as they happen; `run()`\nis implemented on top of it, so both share one code path.\n\n```ts\nfor await (const event of stream(agent, 'hi')) {\n    switch (event.type) {\n        case 'text_delta': /* ... */ break\n        case 'tool_start':\n        case 'tool_end': /* ... */ break\n        case 'handoff': /* ... */ break\n        case 'guardrail_triggered': /* ... */ break\n        case 'run_completed':\n        case 'run_failed': /* ... */ break\n    }\n}\n```\n\n**Tracing** - every `RunResult` carries a `trace`: run ID, a span per model call/tool call/handoff/\nguardrail hit/output-repair retry, timing, and aggregated token usage.\n\n```ts\nconst result = await run(agent, '...')\nconsole.log(result.trace.tokenUsage) // { promptTokens, completionTokens, totalTokens }\nconsole.log(result.trace.spans) // full span list with timing and errors\n```\n\n**Model providers** - the runner talks to a provider-agnostic `ModelProvider` interface, not\ndirectly to any single SDK's types. Three real adapters ship today, plus a network-free one for\ntests and examples:\n\n```ts\nimport { openai, anthropic, gemini, mockProvider } from '@abhijeetkadam/redai'\n\nopenai({ model: 'gpt-4o' })                  // reads OPENAI_API_KEY\nanthropic({ model: 'claude-opus-4-6' })       // reads ANTHROPIC_API_KEY\ngemini({ model: 'gemini-2.0-flash' })         // reads GEMINI_API_KEY\nmockProvider([{ content: 'canned response' }]) // no network, no key\n```\n\nSwapping providers is a one-line change to `Agent.model` - nothing else in your code changes,\nsince the runner only ever depends on the `ModelProvider` interface. Adding a fourth provider\nmeans implementing that same interface (`name`, `generate`, `stream`) - no runner changes needed.\n\n**Error handling** - `run()`/`stream()` never throw for expected failure modes; they resolve to\n`{ ok: false, error }`. `error` is always one of these `AgentError` subclasses:\n\n| Error | When |\n|---|---|\n| `ToolNotFoundError` | model called a tool name that isn't registered |\n| `ToolInputValidationError` | tool arguments failed the Zod input schema |\n| `ToolExecutionError` | a tool's `execute` threw |\n| `OutputValidationError` | final answer never matched `outputSchema`, even after repair retries |\n| `GuardrailViolationError` | an input/output/tool guardrail rejected the content |\n| `HandoffLoopError` | self-handoff or `maxHandoffs` exceeded |\n| `ModelProviderError` | the underlying provider SDK threw (network, auth, rate limit, etc.) |\n| `MaxTurnsExceededError` | no final answer within `agent.maxTurns` |\n\nNote that `ToolNotFoundError`/`ToolInputValidationError`/`ToolExecutionError` reaching your code\nvia `result.error` only happens for a tool call *outside* the normal loop (there isn't one -\ninside the loop these are caught and fed back to the model as a tool message so it can recover,\nwhich is why [`examples/tracing.ts`](examples/tracing.ts)'s \"broken tool\" case still ends in\n`ok: true`). You'll see these types directly on `result.error` for the other listed cases, and\ninside `result.trace.spans[].error` either way.\n\n```ts\nimport { GuardrailViolationError } from '@abhijeetkadam/redai'\n\nconst result = await run(agent, 'do something')\nif (!result.ok) {\n    if (result.error instanceof GuardrailViolationError) {\n        // handle rejection specifically\n    }\n    console.error(result.error.name, result.error.message)\n}\n```\n\n## Examples\n\n| File | Demonstrates |\n|---|---|\n| [`examples/basic.ts`](examples/basic.ts) | Core loop, a real async tool call |\n| [`examples/sessions.ts`](examples/sessions.ts) | Multi-turn history via `Session` |\n| [`examples/structured-output.ts`](examples/structured-output.ts) | Schema validation + repair retry |\n| [`examples/guardrails.ts`](examples/guardrails.ts) | Input/output/tool guardrails |\n| [`examples/handoffs.ts`](examples/handoffs.ts) | Delegation, self-handoff block, loop cap |\n| [`examples/streaming.ts`](examples/streaming.ts) | Live event stream |\n| [`examples/tracing.ts`](examples/tracing.ts) | Full trace inspection, token usage, error spans |\n| [`examples/providers.ts`](examples/providers.ts) | Constructs OpenAI/Anthropic/Gemini adapters, confirms each satisfies `ModelProvider` |\n\nRun any of them with `npm run example:<name>` (e.g. `npm run example:handoffs`).\n\n## Development\n\n```bash\nnpm run typecheck   # tsc --noEmit\nnpm run build       # tsup -> dist/ (ESM + CJS + .d.ts)\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}