genaicode
Health Gecti
- License — License: Apache-2.0
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 47 GitHub stars
Code Uyari
- process.env — Environment variable access in examples/basic.ts
- process.env — Environment variable access in examples/conversation-loop.ts
- process.env — Environment variable access in examples/cron-job.ts
- process.env — Environment variable access in examples/http-handler.ts
- process.env — Environment variable access in examples/queue-worker.ts
- fs module — File system access in package.json
- process.env — Environment variable access in scripts/build-ui.mjs
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
One API and CLI over every coding agent (Claude Code, Codex, Copilot, Cursor, Gemini…), plus a thin provider-neutral LLM layer for TypeScript.”
GenAIcode
GenAIcode drives the coding-agent CLIs you already have (Claude Code, Codex, GitHub Copilot
CLI, Cursor, Gemini CLI, opencode, Muse, Mistral Vibe, Antigravity) behind one API and one
CLI. Script them, swap them with one flag, and let your tests decide when the job is done.
Recorded live with Claude Code and Codex. Full video (87 s).
npx genaicode run --verify "npm test" "fix the failing tests"
That runs the first agent CLI installed on your machine in the current directory, then runsnpm test. If the check fails, its output goes back to the agent for another attempt. Add-a codex (or claude, copilot, opencode...) to pick the agent; the command stays the
same. From there, the same drivers are a TypeScript API, a chat in your terminal or browser,
a panel inside your Vite app, and an agent inside your own app.
GenAIcode also ships a thin LLM layer for calling model providers
from backend code. The two halves are separate subpaths: importing genaicode never spawns an
agent, and genaicode/agents loads no provider SDK.
Install
npm install genaicode
Node.js 20 or newer is required. The agent CLIs are yours to install and log in to;npx genaicode agents lists which ones it finds.
Coding agents
genaicode/agents turns the coding-agent CLIs installed on a machine into one API. Give
any of them a task (a prompt and a working directory) and get back the same event stream
and result, whichever agent does the work. It is how you script, compare, or chain coding
agents from a job, a bot, or a CI step without writing a parser per vendor.
import { claude, codex, copilot, cursor, detectAgents } from 'genaicode/agents';
const [found] = detectAgents([claude(), codex(), copilot(), cursor()]).filter(({ path }) => path);
if (!found) throw new Error('No coding agent installed.');
const run = found.agent.run({ prompt: 'Add a unit test for parseDate', cwd: '/path/to/repo', timeoutMs: 20 * 60_000 });
for await (const event of run) {
if (event.type === 'tool-start') console.log('⚙', event.name);
if (event.type === 'file-change') console.log('edited', event.paths.join(', '));
}
const result = await run.result; // { status, ok, text, sessionId, usage, error, ... }
| Agent | Driver | Extras |
|---|---|---|
| Claude Code | claude() |
MCP servers, approvals, max turns, effort, cost |
| Codex | codex(), codexLive() |
MCP servers; live: steer(), approvals |
| GitHub Copilot | copilot() |
MCP servers, allow/deny tool patterns |
| Cursor | cursor() |
streamed text deltas |
| Gemini CLI | gemini() |
approval mode |
| opencode | opencode() |
provider/model ids, variants |
| Mistral Vibe | vibe() |
agent profile, max turns, max price |
| Antigravity CLI | antigravity() |
sandbox, effort, usage |
| Muse | muse(), museLive() |
max turns; live: steer(), approvals |
| Your own CLI/API | cliAgent(), hostedAgent() |
plug in any JSON-lines CLI or hosted agent |
On top of the drivers:
- Steer a running task with
run.steer(text)(live drivers), and answer permission
prompts withonApproval(Claude, live Codex and Muse). - Pick approval and sandbox policy with
permissions: { approval, sandbox }(or'yolo'); each driver maps it to the agent's own flags and refuses what it cannot honor. - Attach MCP servers per task with
mcpServers. Header secrets stay out of argv. - Verify and repair:
runWithVerifyruns your check (tests, lint) after the agent and
sends failures back for another attempt. - Keep credentials apart:
withoutProviderCredentials()strips your app's LLM API keys
from the agent's environment, so it bills its own login.
Importing genaicode never spawns anything; only genaicode/agents does. The agent uses
its own credentials and billing, and its own permission settings unless permissions sets
them; any sandbox is the agent's own. GenAIcode does not choose a model or retry it. See docs/agents.md for events, options per
driver, and writing your own driver.
From the command line
The genaicode bin runs the same headless drivers without writing any code:
npx genaicode agents # which agent CLIs are installed
npx genaicode run "Add a unit test for parseDate" # first installed agent, current dir
npx genaicode run -a codex -C ../api --verify "npm test" "Fix the failing date tests"
git diff | npx genaicode run --json -a claude - # prompt from stdin, JSON-lines events
Assistant text goes to stdout and tool calls, edits, and the result line go to stderr.--verify runs a shell command after the agent finishes and sends its output back for up
to --max-repairs more attempts (runWithVerify). The exit code is 0 on success, 1 when
the agent or the check fails, 2 on a usage error, and 130 when interrupted.npx genaicode --help lists every option.
Chat with an agent: terminal and browser
npx genaicode # chat in this terminal (same as "genaicode chat")
npx genaicode chat -a claude -C ../api
npx genaicode ui # the same chat in your browser, served on 127.0.0.1
Each prompt after the first continues the agent's own session (AgentTask.resume), so you
can say "now add a test for that". Prompts sent while the agent works wait in a queue and
run next; Esc stops the agent. /agent, /model and /new switch agent, model and
session; /help lists the rest.
The wolf from genaicode 1.0 is back. When the agent asks for approval or finishes, the
browser UI barks if its tab is in the background and counts those events in the tab title.
The terminal UI rings the terminal bell instead. /bark off (or the Bark button) quiets it.
genaicode ui listens on the loopback address and prints a link with a random access
token. The page, the event stream and every command need that token; only the bundled
script and the four mascot files (/app.js, /assets/…) are served without it, since they
hold nothing about your session. Both front ends are bundled into the package and load
only for these commands; importing genaicode or genaicode/agents pulls in no UI code.
In code, the same conversation is createAgentSession from genaicode/agents (see
docs/agents.md); with a live driver (codexLive,museLive) it also steers the running agent and asks you about permission requests.
Inside a Vite app
// vite.config.ts
import { defineConfig } from 'vite';
import genaicode from 'genaicode/vite';
export default defineConfig({ plugins: [genaicode()] });
In vite dev, the app gets the wolf in its corner. It opens the same chat as genaicode ui
in a panel, with the agent working in the project root. The wolf counts the page's errors
(build errors, uncaught exceptions, unhandled rejections, console.error), and "Fix N
errors" sends them to the agent as one prompt. Options: agent, model, effort,approveAll, agents, port, and captureErrors: false to leave the page's errors alone.
Builds are untouched. The chat runs on its own port on 127.0.0.1 even when Vite runs with--host, lets only the app's origins frame it, and hands its token (and takes "fix" requests)
only from the app's own pages on this machine.
In your own React app
genaicode/react is the same chat as a component. It draws a SessionState your app gives
it and calls you back; it runs, fetches and stores nothing, so your server owns the session,
its MCP servers and its prompts. React 18 or newer comes from your app: genaicode does not
declare or install it.
// server (Node): one session, streamed to the page
import { createAgentSession, claude } from 'genaicode/agents';
const session = createAgentSession({
agent: claude(),
cwd: process.cwd(),
task: { mcpServers: [{ name: 'ops', url: 'http://127.0.0.1:4000/mcp' }] },
// The agent gets the context; the transcript keeps what the user typed.
transformPrompt: (text) => `${briefing}\n\nOperator message:\n${text}`,
});
app.get('/api/agent/events', (req, res) => {
res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-store' });
const push = (state) => res.write(`data: ${JSON.stringify(state)}\n\n`);
push(session.get());
res.on('close', session.subscribe(push));
});
app.post('/api/agent/send', (req, res) => {
session.send(req.body.text);
res.json({ ok: true });
});
app.post('/api/agent/stop', (req, res) => {
session.stop();
res.json({ ok: true });
});
app.post('/api/agent/approve', (req, res) => res.json({ ok: session.approve(req.body.id, req.body.decision) }));
// page
import { useEffect, useState } from 'react';
import { AgentChat, type SessionState } from 'genaicode/react';
import 'genaicode/react/styles.css';
const post = (path: string, body: object = {}) =>
fetch(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
export function AgentDrawer() {
const [state, setState] = useState<SessionState | null>(null);
useEffect(() => {
const events = new EventSource('/api/agent/events');
events.onmessage = (event) => setState(JSON.parse(event.data));
return () => events.close();
}, []);
return (
<AgentChat
state={state}
onSend={(text) => post('/api/agent/send', { text })}
onStop={() => post('/api/agent/stop')}
onApprove={(id, decision) => post('/api/agent/approve', { id, decision })}
formatToolName={(name) => name.replace(/^mcp__ops__/, '')}
renderFooter={() => <MyApprovalCards />}
headerExtras={<button onClick={() => post('/api/agent/reset')}>New</button>}
/>
);
}
<AgentChat> fills its container's height. It shows turns with streaming Markdown (no raw
HTML; links open in a new tab), collapsible tool calls, edited files, errors, the agent's
own approval requests, queued prompts and a working indicator, and an input where Enter
sends, Shift+Enter adds a line and Esc stops. Props:
| Prop | |
|---|---|
state |
SessionState, or null before there is a session |
onSend(text), onStop() |
Enter or Send; Stop or Esc. No onStop, no Stop button |
onApprove(id, decision) |
answers state.approvals; without it they show as waiting |
renderPrompt(text, turn?) |
how a sent prompt shows (return null to hide it) |
renderAfterTurn(turn, i), renderFooter() |
your content after a turn, or at the end |
notices |
{ id, afterTurn, text, tone? }[]: your own lines between turns |
foldSteps |
fold each turn's tools and edits into one "N steps" line |
header, headerExtras |
replace the header (null for none), or add to its right end |
emptyState, placeholder, hint |
text before the first turn, in and under the input |
formatToolName(name), disabled, autoFocus, theme, className, style |
Theme it with CSS custom properties on the component or any ancestor: --gc-bg,--gc-panel, --gc-border, --gc-text, --gc-muted, --gc-accent, --gc-accent-text,--gc-error, --gc-font, --gc-mono, --gc-font-size, --gc-radius, --gc-max-width
(and --gc-ok, --gc-warn, plus soft shades mixed from these unless set). Unset ones keep
the genaicode ui look, light or dark by prefers-color-scheme, or by theme="light" /"dark". genaicode ui and the Vite panel draw this same component.
Two smaller pieces come along for hosts that build their own panels around a transcript:
import { installResizeHandle, storedSize, useFollowScroll } from 'genaicode/react';
// A log that stays at its newest line until the user scrolls up (and again after follow()).
const { ref, onScroll, follow } = useFollowScroll(lines);
// A drag handle on a panel's left edge; the width survives reloads.
const stored = storedSize('panel-width', [300, 900]);
let width = stored.get();
const handle = installResizeHandle(edge, {
axis: 'x',
direction: () => -1, // dragging left makes the panel wider
bounds: () => [300, Math.min(900, innerWidth - 24)],
read: () => width ?? 390,
write: (next, commit) => {
width = next;
panel.style.setProperty('--width', `${width}px`);
if (commit) stored.set(width);
},
reset: () => {
width = undefined;
panel.style.removeProperty('--width');
stored.clear();
},
});
// The CSS caps the shown width, so the saved one comes back when the window grows again:
// .panel { width: min(var(--width, 390px), calc(100vw - 24px)); }
window.addEventListener('resize', handle.sync);
The handle keeps its aria-value* range current and takes the arrow keys (Shift for bigger
steps), Home and End, Escape to undo a drag, and Enter or double-click to reset. While a drag
runs, document.body (or root) has data-resizing="true", so the page can keep the cursor
and turn off pointer events on iframes. read() must return what write or reset just set,
so keep the size in a variable or a ref rather than only in React state.
To show the whole genaicode ui page in a frame instead, startEmbeddedWeb fromgenaicode/ui serves it on 127.0.0.1 with your task (mcpServers, timeoutMs...), env
and transformPrompt, and returns its tokenized url and a controller to send prompts.
Inside your own local app
genaicode/app-agent puts an agent in a local app you run on your machine (an admin
console, an internal tool, a game editor). The user chats with an agent CLI they already
have; the agent works the app through tools the app serves over MCP, changes wait for the
user's approval, and in dev mode the agent also edits the app's code and proposes a commit.
import { createServer } from 'node:http';
import { createAppAgent, createAppAgentHandler } from 'genaicode/app-agent';
const agent = createAppAgent({
name: 'the shop admin',
mcpUrl: 'http://127.0.0.1:3000/api/agent/mcp',
tools: [
{ name: 'orders', description: 'List open orders.', run: () => JSON.stringify(listOrders()) },
{
name: 'refund',
description: 'Refund an order.',
write: true, // a card with Approve and Reject; runs only after Approve
inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
run: ({ id }) => refund(String(id)),
},
],
pages: () => ['/orders', '/customers'], // page context, link chips, "take me there"
dev: { cwd: process.cwd(), commands: ['npm test'] }, // optional: the agent edits the app
});
const handle = createAppAgentHandler(agent, { authorize: (req) => isSignedIn(req) });
createServer(async (req, res) => {
if (await handle(req, res)) return;
// ...the rest of the app
}).listen(3000, '127.0.0.1');
On the page, AppAgentChat from genaicode/react is the whole drawer: the chat, the agent's
page chips under its replies, Approve and Reject cards (with the diff of a proposed commit),
and the agent and mode pickers. It reports currentPath to the agent and calls onNavigate
when a chip is clicked or the agent is asked to go somewhere.
import { AppAgentChat } from 'genaicode/react';
import 'genaicode/react/styles.css';
<AppAgentChat currentPath={location.pathname} onNavigate={(path) => router.push(path)} />;
Under it, /api/agent serves GET /events (a stream of AppAgentState: the session, earlier
turns, pending actions, link chips, navigation requests) and POST /send, /stop, /reset,/config, /view, /actions/:id and /approvals/:id (the agent CLI's own permission
prompts); useAppAgent wraps them for a page of your own. Switching agent or mode keeps the
conversation: the next agent gets a short transcript. trace receives every prompt, MCP call,
decision and finished turn, for a log. Default agents are Claude Code, Codex and
Copilot CLI, locked down per mode: in use mode they get only the app's tools (no shell, no
file edits); in dev mode they edit files and run the listed commands, never commit or push (Claude and
Copilot enforce the command list; Codex has no per-command allowlist, so it runs in itsworkspace-write sandbox: writes only inside cwd, no network).propose_commit snapshots the worktree, the user reviews that diff, and Approve commits
exactly that tree on the reviewed HEAD, or refuses if the files or HEAD changed since, a hook
changed the files, or a merge or rebase is in progress. The repository's commit hooks run.
The agent sees no MCP server but the app's: Claude runs with --strict-mcp-config, and Codex
and Copilot get the user's own configured servers switched off. The MCP endpoint takes a per-run
bearer and refuses browser requests; every other route asks authorize and refuses
cross-site requests. Without authorize only a loopback Host is accepted (add names withallowedHosts), so a DNS-rebinding page cannot reach the agent.
Also: a thin LLM layer
Besides driving agents, GenAIcode is a small TypeScript toolkit for calling LLMs from backend
code. It sits between raw provider SDKs and full agent frameworks: one prompt representation,
thin provider adapters, a convenient request API, and lightweight conversation chains. This
layer does not inspect repositories, execute shell commands, edit files, or run an agent UI.
Like jQuery, the common case starts with one small function and becomes more specific
through chaining—configure a request, follow up across multiple prompts, and keep history
portable without adopting a full agent framework.
A prompt in three lines
import { genaicode } from 'genaicode';
import { openai } from 'genaicode/providers';
const ai = genaicode(openai({ model: 'your-model-name' }));
const answer = await ai('Explain why the sky is blue in two sentences.').text();
The client is callable on purpose. Configuration is ordinary method chaining—system
instructions, temperature, token limits, and more—without a separate options object or
framework setup:
const result = await ai('Create a release note from these commits')
.system('You are a concise technical writer.')
.temperature(0.2)
.maxOutputTokens(500)
.text();
Builders are immutable, so a configured base request can be safely reused:
const releaseNote = ai('Create a release note from these commits')
.system('You are a concise technical writer.')
.temperature(0.2);
const short = await releaseNote.maxOutputTokens(200).text();
const long = await releaseNote.maxOutputTokens(800).text();
Why this vs a raw SDK
Provider SDKs are the right tool when you call one vendor and own every request shape
yourself. The cost shows up when the same backend needs a second provider, a test double,
or shared policy (timeouts, JSON parsing, rate limits, fallback) without copying glue.
GenAIcode is that thin shared layer:
- One portable prompt/tool IR (
PromptItem) instead of OpenAI messages vs Anthropic
blocks vs Gemini contents. - One request API (
.text(),.json(),.stream(), chains) over those adapters. - A tiny
ModelProviderseam so tests and custom gateways do not mock vendor HTTP. - Opt-in middleware and retry helpers—no hidden tool runners, no silent retries, no
agent loop.
If you are happy importing one SDK and never swapping models or providers, stay on the
SDK. If you want the call site to look the same while the edge stays replaceable, use
GenAIcode.
Chaining prompts
A chain remembers successful user and assistant turns. Each new prompt sees the complete
history, so multi-step work stays in ordinary application code:
import { system } from 'genaicode';
const chain = ai.chain(system('You are helping refine an API design.'));
const answer1 = await chain.text('Prompt 1: propose a minimal endpoint.');
const answer2 = await chain.text('Prompt 2: add idempotency to that design.');
const answer3 = await chain.text('Prompt 3: summarize the final contract.');
This is prompt1 → response1 → prompt2 → response2 → prompt3 → response3. The history
is available as portable PromptItem[] through chain.history().
Using a chain in a loop
A chain is ordinary application state, so normal control flow works:
const chain = ai.chain(system('Improve the draft while preserving its meaning.'));
let draft = 'The deployment had a problem and we fixed it.';
for (const instruction of ['Make it specific.', 'Make it concise.', 'Use a professional tone.']) {
draft = await chain.text(`${instruction}\n\nCurrent draft:\n${draft}`);
}
Calls on one chain are serialized, even if application code starts them concurrently.
Failed provider calls are not added to history.
Validation and repair are also plain loops rather than a special agent abstraction:
import { parseJsonResult, resultText, system } from 'genaicode';
const repair = ai.chain(system('Return only valid JSON with a non-negative count.'));
let count: number | undefined;
for (let attempt = 1; attempt <= 3; attempt += 1) {
const result = await repair.ask(
attempt === 1 ? 'Count the actionable items in this text: ...' : 'Correct the previous response.',
);
try {
const value = parseJsonResult<{ count: number }>(result);
if (value.count < 0) throw new Error('count must be non-negative');
count = value.count;
break;
} catch (error) {
if (attempt === 3) throw error;
console.warn('Invalid model response:', resultText(result));
}
}
The application owns validation, attempt limits, and failure policy.
Schema adapters
json(...) and parseJsonResult(...) accept either a parser function or a schema adapter
with a parse(value) method. This keeps schema validation library-agnostic:
const value = await ai('Return {"count": 3}.').json({
parse(input) {
if (typeof input !== 'object' || input === null || typeof input.count !== 'number') {
throw new Error('Invalid shape');
}
return input;
},
});
Calling .json() also sets responseFormat: { type: 'json' } on the request when you have
not already chosen a format, so providers that support JSON mode (OpenAI, Gemini/Vertex)
are asked for JSON rather than free text.
Response format and thinking
Portable request fields cover the two knobs backends usually poke through provider-specific
config:
const verdict = await ai(promptText)
.responseFormat({ type: 'json' })
.thinking({ level: 'minimal' }) // or { budgetTokens: 0 } / false to disable
.temperature(0)
.json((value) => VerdictSchema.parse(value));
responseFormat:{ type: 'text' | 'json' }or{ type: 'json_schema', name, schema, strict? }.thinking:falseto disable, or{ budgetTokens?, level? }(minimal|low|medium|high). Prefer one of budget or level — some providers reject both.
On Google/Gemini,falseandbudgetTokens: 0map tothinkingLevel: MINIMAL
(orLOWfor models like Gemini 3.7 and Gemini 3 Pro whereMINIMALis unsupported)
because Gemini 3 rejectsthinkingBudget: 0. JSONresponseFormatwithout an
explicitthinkingsetting or providergenerationConfig.thinkingConfigdefault also
defaults Google thinking toMINIMAL(orLOWon Gemini 3.7 / Pro).
On Anthropic,levelbecomesoutput_config.effortwith adaptive thinking (Opus and
Sonnet 4.6+), andfalseuses whatever the model accepts:disabled,between_tools
(Sonnet 5.5), or effortlowwhere thinking is always on (Opus 5.5, Fable). Budgets
become adaptive thinking on models that removed them.
Providers map what they support and ignore the rest. ProviderCapabilities.jsonResponse
and ProviderCapabilities.thinking advertise support. Vendor-specific escapes such as
Vertex generationConfig remain available for anything not covered here.
PromptItem: the portable prompt IR
PromptItem is GenAIcode's provider-neutral intermediate representation:
import { image, prompt, system, toolResults, user } from 'genaicode';
const items = prompt(
system('Return a one-sentence caption.'),
user('Describe this image.', {
images: [image(base64Png, 'image/png')],
}),
toolResults({
callId: 'lookup-1',
name: 'lookup',
content: JSON.stringify({ locale: 'pl-PL' }),
}),
);
const caption = await ai(items).text();
Applications can make domain objects prompt-aware without coupling them to a provider:
import { asPrompt, system, user } from 'genaicode';
const supportTicket = (ticket: Ticket) =>
asPrompt(() => [system('Triage support tickets.'), user(JSON.stringify(ticket))]);
await ai(supportTicket(ticket)).text();
Tools
const calls = await ai('What is the weather in Warsaw?')
.tools([
{
name: 'weather',
description: 'Get current weather',
parameters: {
type: 'object',
properties: { city: { type: 'string' } },
required: ['city'],
additionalProperties: false,
},
},
])
.toolCalls();
GenAIcode normalizes tool calls but deliberately does not execute them. The application
owns permissions, retries, and side effects.
Streaming
Providers that support native streaming expose a provider-neutral StreamEvent IR.
Request builders and chains offer .stream() and .streamText(). If a provider has nostream method, GenAIcode synthesizes a short stream from generate.
for await (const event of ai('Write a haiku about queues.').stream()) {
if (event.type === 'text-delta') process.stdout.write(event.text);
if (event.type === 'done') console.log('\n', event.result.usage);
}
Built-in adapters declare capability metadata:
ai.provider.capabilities;
// { streaming: true, tools: true, images: 'input', systemPrompt: true }
Middleware
Hook-style plugins remain the extension point. Built-in helpers cover common backend
needs without restoring a global registry:
import { cachePlugin, fallbackPlugin, genaicode, rateLimitPlugin, timingPlugin } from 'genaicode';
import { anthropic, openai } from 'genaicode/providers';
const ai = genaicode(openai({ model: 'your-model-name' }), {
plugins: [
timingPlugin(),
rateLimitPlugin({ concurrency: 2, minIntervalMs: 50 }),
cachePlugin({ maxEntries: 64 }),
fallbackPlugin({ providers: [anthropic({ model: 'your-claude-model' })] }),
],
});
Retries
Retries stay in application code. Use classifyError, isRetryable, and withRetry
when you want shared classification without hidden policy:
import { withRetry } from 'genaicode';
const text = await withRetry(() => ai('Summarize the deploy notes.').text(), {
attempts: 3,
delayMs: 250,
});
See retry guidance, semver policy, and the
provider package evaluation.
Providers
Models are provided in the adapter, through .model(...), or with environment variables:
| Adapter | Factory | Environment defaults |
|---|---|---|
| OpenAI | openai() |
OPENAI_API_KEY, OPENAI_MODEL |
| Anthropic | anthropic() |
ANTHROPIC_API_KEY, ANTHROPIC_MODEL |
| Gemini API / AI Studio | gemini() |
GEMINI_API_KEY or API_KEY, GEMINI_MODEL |
| Vertex AI | vertexAI() |
GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION, VERTEX_AI_MODEL |
| OpenAI-compatible | openaiCompatible() |
Explicit options; OpenAI defaults also work |
import { anthropic, gemini, openai, vertexAI } from 'genaicode/providers';
const viaOpenAI = genaicode(openai({ model: 'your-openai-model' }));
const viaClaude = genaicode(anthropic({ model: 'your-claude-model' }));
const viaGemini = genaicode(gemini({ model: 'your-gemini-model' }));
const viaVertex = genaicode(
vertexAI({
project: 'my-google-cloud-project',
location: 'global',
model: 'your-vertex-model',
}),
);
Provider-specific options stay at provider construction. For example, Anthropic exposes
thinking and output-token defaults, while Gemini and Vertex expose SDK HTTP, auth, API
version, and generation configuration options.
GitHub Models, Ollama, vLLM, and other compatible gateways use the OpenAI-compatible
adapter:
import { openaiCompatible } from 'genaicode/providers';
const local = genaicode(
openaiCompatible({
name: 'local',
baseURL: 'http://localhost:11434/v1',
apiKey: 'local',
model: 'qwen3',
}),
);
Implementing a provider directly is intentionally small:
import type { ModelProvider } from 'genaicode';
const provider: ModelProvider = {
name: 'internal-gateway',
async generate(request) {
// Convert request.prompt to your API and normalize the response.
return { parts: [{ type: 'text', text: 'response' }] };
},
};
All Anthropic, Google, and OpenAI conversion functions are public fromgenaicode/providers, so gateways and tests can reuse them without instantiating clients.
Provider E2E tests
Run real-provider E2E tests locally with:
npm run test:e2e
These tests are credential-gated and run only when provider-specific environment variables
are set:
- OpenAI:
OPENAI_API_KEY,OPENAI_MODEL - Anthropic:
ANTHROPIC_API_KEY,ANTHROPIC_MODEL - Gemini:
GEMINI_API_KEY,GEMINI_MODEL
Beyond the smoke call, E2E also covers portable responseFormat: { type: 'json' }
(OpenAI, Gemini) and thinking (Anthropic disable; Gemini disable / level: 'minimal').
CI/CD is configured in .github/workflows/provider-e2e.yaml. Each provider runs in its own
job and only starts when both required secrets are configured in GitHub Actions.
Plugins and hooks
A provider plugin no longer needs registration in global configuration. It exports the
same small ModelProvider contract as a built-in adapter:
// @acme/genaicode-bedrock
import type { ModelProvider } from 'genaicode';
export const bedrock = (options: BedrockOptions): ModelProvider => ({
name: 'bedrock',
capabilities: { streaming: false, tools: true, systemPrompt: true },
async generate(request) {
// Convert PromptItem[], call Bedrock, and return GenerationResult.
return { parts: [{ type: 'text', text: 'response' }] };
},
});
Hook-style plugins use middleware. They can observe or rewrite requests and results,
handle errors, implement caches or fallbacks, and intentionally short-circuit a call.
Optional stream middleware follows the same registration order.
import { definePlugin, genaicode } from 'genaicode';
import { openai } from 'genaicode/providers';
const timing = definePlugin({
name: 'timing',
async generate(request, next) {
const startedAt = performance.now();
try {
return await next(request);
} finally {
console.log('LLM request took', performance.now() - startedAt, 'ms');
}
},
});
const ai = genaicode(openai({ model: 'your-model-name' }), {
plugins: [timing],
});
Plugins run in registration order, and each plugin may call next() once. This keeps the
extension mechanism compatible with npm packages and ordinary imports without restoring
the 1.x runtime TypeScript loader or process-global plugin registry.
Framework-shaped examples live under examples/ (http-handler, queue-worker,cron-job).
Design boundaries
- Backend library, not a coding agent.
genaicode/agentsdrives external agent CLIs; it
adds no agent loop, prompts, or repository tools of its own. - Provider-neutral core with no global configuration.
- Explicit models and credentials; environment variables are only provider defaults.
- Immutable request builders.
- Conversation history is explicit; loops remain ordinary application code.
- No hidden tool execution or hidden retries.
- Third-party providers and middleware use stable TypeScript contracts.
- Provider SDKs stay behind the
genaicode/providerssubpath; process spawning stays
behindgenaicode/agents.
Migration from 1.x
GenAIcode 1.x was a coding agent. Version 2.0 deliberately replaces that product with a
small backend LLM toolkit: a jQuery-like layer for portable prompts, provider adapters,
conversation chains, and plugins.
The 1.x coding agent, browser UI, repository tools, and shell execution are not
deprecated compatibility features; they have been removed from 2.0. The 2.xgenaicode command only drives external agent CLIs (see From the command line). The GenAIcode name, PromptItem model, provider converters, and extensibility
continue here in a smaller and more focused form.
The original coding agent remains available from the preserved1.x branch and the 1.x npm releases:
npx genaicode@1
In 2.x, npx genaicode opens a chat with the coding-agent CLIs you have installed
(Claude Code, Codex, opencode...) instead of the 1.x built-in agent.
See the pivot plan for the full scope, migration decisions, and the
roadmap.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi