genaicode

agent
Guvenlik Denetimi
Uyari
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.

SUMMARY

One API and CLI over every coding agent (Claude Code, Codex, Copilot, Cursor, Gemini…), plus a thin provider-neutral LLM layer for TypeScript.”

README.md

GenAIcode Logo.

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.

genaicode in 20 seconds: the Vite overlay fixes a broken page, run --verify sends a failed test back to the agent, and an agent inside an app waits for Approve.
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 runs
npm 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 with onApproval (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: runWithVerify runs 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.

genaicode chat in a terminal: the agent fixes a failing test, then takes queued follow-ups. genaicode ui in a browser: the same fix and a follow-up.

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.

The browser UI with the wolf behind a finished turn.

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()] });

The genaicode panel over a Vite app: the agent fixed the page's two errors.

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 from
genaicode/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)} />;

A shop admin with the agent drawer: the agent proposed a refund, waiting for Approve.

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 its
workspace-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 with
allowedHosts), 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 ModelProvider seam 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: false to disable, or { budgetTokens?, level? } (minimal |
    low | medium | high). Prefer one of budget or level — some providers reject both.
    On Google/Gemini, false and budgetTokens: 0 map to thinkingLevel: MINIMAL
    (or LOW for models like Gemini 3.7 and Gemini 3 Pro where MINIMAL is unsupported)
    because Gemini 3 rejects thinkingBudget: 0. JSON responseFormat without an
    explicit thinking setting or provider generationConfig.thinkingConfig default also
    defaults Google thinking to MINIMAL (or LOW on Gemini 3.7 / Pro).
    On Anthropic, level becomes output_config.effort with adaptive thinking (Opus and
    Sonnet 4.6+), and false uses whatever the model accepts: disabled, between_tools
    (Sonnet 5.5), or effort low where 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 no
stream 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 from
genaicode/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/agents drives 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/providers subpath; process spawning stays
    behind genaicode/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.x
genaicode 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 preserved
1.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)

Sonuc bulunamadi