genaicode 2.18.3 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -856
- package/dist/cli/embed.d.ts +1 -1
- package/dist/cli/embed.js +2 -2
- package/dist/cli/embed.js.map +1 -1
- package/dist/cli/main.d.ts +1 -1
- package/dist/cli/main.js +1 -1
- package/dist/overlay/client.js +3 -1
- package/dist/overlay/client.js.map +1 -1
- package/dist/react/agent-chat.css +39 -0
- package/dist/react/agent-chat.d.ts +2 -0
- package/dist/react/agent-chat.js +2 -2
- package/dist/react/agent-chat.js.map +1 -1
- package/dist/react/markdown.js +69 -7
- package/dist/react/markdown.js.map +1 -1
- package/dist/ui/index.js +70 -68
- package/dist/ui/web-client.js +50 -11
- package/docs/CHANGELOG.md +12 -0
- package/docs/agents.md +51 -0
- package/docs/app-agent.md +71 -0
- package/docs/cli.md +105 -0
- package/docs/llm.md +460 -0
- package/docs/react.md +134 -0
- package/{skills → docs/skills}/genaicode-embed/SKILL.md +2 -1
- package/docs/vite.md +42 -0
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
<picture>
|
|
3
3
|
<source media="(prefers-color-scheme: dark)" srcset="docs/media/logo-dark.png">
|
|
4
4
|
<source media="(prefers-color-scheme: light)" srcset="docs/media/logo.png">
|
|
5
|
-
<img alt="GenAIcode Logo." src="docs/media/logo.png" width="
|
|
5
|
+
<img alt="GenAIcode Logo." src="docs/media/logo.png" width="320">
|
|
6
6
|
</picture>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
CLI. Script them, swap them with one flag, and let your tests decide when the job is done.
|
|
9
|
+
<p align="center">
|
|
10
|
+
<b>One API and one CLI for the coding agents you already have.</b><br>
|
|
11
|
+
Claude Code · Codex · Copilot CLI · Cursor · Gemini CLI · opencode · Muse · Mistral Vibe · Antigravity
|
|
12
|
+
</p>
|
|
14
13
|
|
|
15
14
|
<p align="center">
|
|
16
15
|
<a href="docs/media/showcase.mp4">
|
|
@@ -20,189 +19,43 @@ CLI. Script them, swap them with one flag, and let your tests decide when the jo
|
|
|
20
19
|
<sub>Recorded live with Claude Code and Codex. <a href="docs/media/showcase.mp4">Full video (87 s)</a>.</sub>
|
|
21
20
|
</p>
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
npx genaicode run --verify "npm test" "fix the failing tests"
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
That runs the first agent CLI installed on your machine in the current directory, then runs
|
|
28
|
-
`npm test`. If the check fails, its output goes back to the agent for another attempt. Add
|
|
29
|
-
`-a codex` (or `claude`, `copilot`, `opencode`...) to pick the agent; the command stays the
|
|
30
|
-
same. From there, the same drivers are a TypeScript API, a chat in your terminal or browser,
|
|
31
|
-
a panel inside your Vite app, and an agent inside your own app.
|
|
32
|
-
|
|
33
|
-
GenAIcode also ships [a thin LLM layer](#also-a-thin-llm-layer) for calling model providers
|
|
34
|
-
from backend code. The two halves are separate subpaths: importing `genaicode` never spawns an
|
|
35
|
-
agent, and `genaicode/agents` loads no provider SDK.
|
|
36
|
-
|
|
37
|
-
## Install
|
|
22
|
+
## Quick start
|
|
38
23
|
|
|
39
24
|
```bash
|
|
40
|
-
|
|
25
|
+
npx genaicode # chat with your agent in this terminal
|
|
26
|
+
npx genaicode ui # the same chat in your browser
|
|
27
|
+
npx genaicode run --verify "npm test" "fix the failing tests"
|
|
28
|
+
npx genaicode embed # let your agent wire genaicode into your app
|
|
41
29
|
```
|
|
42
30
|
|
|
43
|
-
|
|
44
|
-
`
|
|
31
|
+
`run --verify` runs the first agent CLI it finds, then your check; a failing check goes back
|
|
32
|
+
to the agent for another try. `-a codex` (or `claude`, `copilot`, `opencode`...) picks the
|
|
33
|
+
agent, and the command stays the same. Node.js 20+; the agent CLIs are yours to install and
|
|
34
|
+
log in to (`npx genaicode agents` lists what it finds).
|
|
45
35
|
|
|
46
|
-
##
|
|
36
|
+
## What's inside
|
|
47
37
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
| Use it as | Import / command | Docs |
|
|
39
|
+
| ------------------------------ | -------------------------------------- | --------------------------------- |
|
|
40
|
+
| A TypeScript API over agents | `genaicode/agents` | [agents.md](docs/agents.md) |
|
|
41
|
+
| CLI, terminal and browser chat | `genaicode run`, `chat`, `ui`, `embed` | [cli.md](docs/cli.md) |
|
|
42
|
+
| A panel in your dev server | `genaicode/vite`, `createDevOverlay` | [vite.md](docs/vite.md) |
|
|
43
|
+
| A chat component | `genaicode/react` `<AgentChat>` | [react.md](docs/react.md) |
|
|
44
|
+
| An agent inside your own app | `genaicode/app-agent` | [app-agent.md](docs/app-agent.md) |
|
|
45
|
+
| A thin LLM layer | `genaicode`, `genaicode/providers` | [llm.md](docs/llm.md) |
|
|
52
46
|
|
|
53
47
|
```ts
|
|
54
|
-
import {
|
|
55
|
-
|
|
56
|
-
const [found] = detectAgents([claude(), codex(), copilot(), cursor()]).filter(({ path }) => path);
|
|
57
|
-
if (!found) throw new Error('No coding agent installed.');
|
|
58
|
-
|
|
59
|
-
const run = found.agent.run({ prompt: 'Add a unit test for parseDate', cwd: '/path/to/repo', timeoutMs: 20 * 60_000 });
|
|
60
|
-
for await (const event of run) {
|
|
61
|
-
if (event.type === 'tool-start') console.log('⚙', event.name);
|
|
62
|
-
if (event.type === 'file-change') console.log('edited', event.paths.join(', '));
|
|
63
|
-
}
|
|
64
|
-
const result = await run.result; // { status, ok, text, sessionId, usage, error, ... }
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
| Agent | Driver | Extras |
|
|
68
|
-
| ---------------- | ----------------------------- | ----------------------------------------------- |
|
|
69
|
-
| Claude Code | `claude()` | MCP servers, approvals, max turns, effort, cost |
|
|
70
|
-
| Codex | `codex()`, `codexLive()` | MCP servers; live: `steer()`, approvals |
|
|
71
|
-
| GitHub Copilot | `copilot()` | MCP servers, allow/deny tool patterns |
|
|
72
|
-
| Cursor | `cursor()` | streamed text deltas |
|
|
73
|
-
| Gemini CLI | `gemini()` | approval mode |
|
|
74
|
-
| opencode | `opencode()` | `provider/model` ids, variants |
|
|
75
|
-
| Mistral Vibe | `vibe()` | agent profile, max turns, max price |
|
|
76
|
-
| Antigravity CLI | `antigravity()` | sandbox, effort, usage |
|
|
77
|
-
| Muse | `muse()`, `museLive()` | max turns; live: `steer()`, approvals |
|
|
78
|
-
| Your own CLI/API | `cliAgent()`, `hostedAgent()` | plug in any JSON-lines CLI or hosted agent |
|
|
79
|
-
|
|
80
|
-
On top of the drivers:
|
|
81
|
-
|
|
82
|
-
- **Steer a running task** with `run.steer(text)` (live drivers), and answer permission
|
|
83
|
-
prompts with `onApproval` (Claude, live Codex and Muse).
|
|
84
|
-
- **Pick approval and sandbox policy** with `permissions: { approval, sandbox }` (or
|
|
85
|
-
`'yolo'`); each driver maps it to the agent's own flags and refuses what it cannot honor.
|
|
86
|
-
- **Attach MCP servers per task** with `mcpServers`. Header secrets stay out of argv.
|
|
87
|
-
- **Verify and repair**: `runWithVerify` runs your check (tests, lint) after the agent and
|
|
88
|
-
sends failures back for another attempt.
|
|
89
|
-
- **Keep credentials apart**: `withoutProviderCredentials()` strips your app's LLM API keys
|
|
90
|
-
from the agent's environment, so it bills its own login.
|
|
91
|
-
|
|
92
|
-
Importing `genaicode` never spawns anything; only `genaicode/agents` does. The agent uses
|
|
93
|
-
its own credentials and billing, and its own permission settings unless `permissions` sets
|
|
94
|
-
them; any sandbox is the agent's own. GenAIcode does not choose a model or retry it. See [docs/agents.md](docs/agents.md) for events, options per
|
|
95
|
-
driver, and writing your own driver.
|
|
96
|
-
|
|
97
|
-
### From the command line
|
|
98
|
-
|
|
99
|
-
The `genaicode` bin runs the same headless drivers without writing any code:
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
npx genaicode agents # which agent CLIs are installed
|
|
103
|
-
npx genaicode run "Add a unit test for parseDate" # first installed agent, current dir
|
|
104
|
-
npx genaicode run -a codex -C ../api --verify "npm test" "Fix the failing date tests"
|
|
105
|
-
git diff | npx genaicode run --json -a claude - # prompt from stdin, JSON-lines events
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Assistant text goes to stdout and tool calls, edits, and the result line go to stderr.
|
|
109
|
-
`--verify` runs a shell command after the agent finishes and sends its output back for up
|
|
110
|
-
to `--max-repairs` more attempts (`runWithVerify`). The exit code is 0 on success, 1 when
|
|
111
|
-
the agent or the check fails, 2 on a usage error, and 130 when interrupted.
|
|
112
|
-
`npx genaicode --help` lists every option.
|
|
113
|
-
|
|
114
|
-
### Chat with an agent: terminal and browser
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
npx genaicode # chat in this terminal (same as "genaicode chat")
|
|
118
|
-
npx genaicode chat -a claude -C ../api
|
|
119
|
-
npx genaicode ui # the same chat in your browser, served on 127.0.0.1
|
|
120
|
-
npx genaicode chat --web # both: the terminal and the browser on one session
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
The terminal and the browser can show one session. `/web` in the terminal chat opens it in
|
|
124
|
-
the browser (`/web off` stops the server), and Enter in the terminal that runs
|
|
125
|
-
`genaicode ui` brings it into that terminal. A prompt, an approval or a stop from either
|
|
126
|
-
side shows up in both.
|
|
127
|
-
|
|
128
|
-
Each prompt after the first continues the agent's own session (`AgentTask.resume`), so you
|
|
129
|
-
can say "now add a test for that". Prompts sent while the agent works wait in a queue and
|
|
130
|
-
run next; Esc stops the agent. When the agent asks for permission, `y` approves, `n`
|
|
131
|
-
denies and `a` approves everything from then on (`/auto`, or the Auto-approve chip in the
|
|
132
|
-
browser; `-y` starts that way). `/agent`, `/model` and `/new` switch agent, model and
|
|
133
|
-
session; `/help` lists the rest.
|
|
48
|
+
import { codex } from 'genaicode/agents';
|
|
134
49
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
<img alt="genaicode chat in a terminal: the agent fixes a failing test, then takes queued follow-ups." src="docs/media/screenshots/chat-terminal.gif" width="49%">
|
|
139
|
-
</picture>
|
|
140
|
-
<picture>
|
|
141
|
-
<source media="(prefers-reduced-motion: reduce) and (prefers-color-scheme: dark)" srcset="docs/media/screenshots/wolf-dark.png">
|
|
142
|
-
<source media="(prefers-reduced-motion: reduce)" srcset="docs/media/screenshots/wolf-light.png">
|
|
143
|
-
<source media="(prefers-color-scheme: dark)" srcset="docs/media/screenshots/chat-browser-dark.gif">
|
|
144
|
-
<source media="(prefers-color-scheme: light)" srcset="docs/media/screenshots/chat-browser-light.gif">
|
|
145
|
-
<img alt="genaicode ui in a browser: the same fix and a follow-up." src="docs/media/screenshots/chat-browser-light.gif" width="49%">
|
|
146
|
-
</picture>
|
|
147
|
-
</p>
|
|
148
|
-
|
|
149
|
-
The wolf from genaicode 1.0 is back. When the agent asks for approval or finishes, the
|
|
150
|
-
browser UI barks if its tab is in the background and counts those events in the tab title.
|
|
151
|
-
The terminal UI rings the terminal bell instead. `/bark off` (or the Bark button) quiets it.
|
|
152
|
-
|
|
153
|
-
<p align="center">
|
|
154
|
-
<picture>
|
|
155
|
-
<source media="(prefers-color-scheme: dark)" srcset="docs/media/screenshots/wolf-dark.png">
|
|
156
|
-
<source media="(prefers-color-scheme: light)" srcset="docs/media/screenshots/wolf-light.png">
|
|
157
|
-
<img alt="The browser UI with the wolf behind a finished turn." src="docs/media/screenshots/wolf-light.png" width="80%">
|
|
158
|
-
</picture>
|
|
159
|
-
</p>
|
|
160
|
-
|
|
161
|
-
`genaicode ui` listens on the loopback address and prints a link with a random access
|
|
162
|
-
token. The page, the event stream and every command need that token; only the bundled
|
|
163
|
-
script and the four mascot files (`/app.js`, `/assets/…`) are served without it, since they
|
|
164
|
-
hold nothing about your session. Both front ends are bundled into the package and load
|
|
165
|
-
only for these commands; importing `genaicode` or `genaicode/agents` pulls in no UI code.
|
|
166
|
-
In code, the same conversation is `createAgentSession` from `genaicode/agents` (see
|
|
167
|
-
[docs/agents.md](docs/agents.md#chat-sessions)); with a live driver (`codexLive`,
|
|
168
|
-
`museLive`) it also steers the running agent and asks you about permission requests.
|
|
169
|
-
|
|
170
|
-
### Let your agent embed it
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
npx genaicode embed # in your project: your agent wires genaicode in
|
|
174
|
-
npx genaicode embed -a codex "only the dev panel"
|
|
175
|
-
npx genaicode embed --print | pbcopy # just the instructions, for any other agent
|
|
50
|
+
const run = codex().run({ prompt: 'Add a unit test for parseDate', cwd: '/path/to/repo' });
|
|
51
|
+
for await (const event of run) if (event.type === 'file-change') console.log('edited', event.paths);
|
|
52
|
+
const result = await run.result; // { status, ok, text, sessionId, usage, ... }
|
|
176
53
|
```
|
|
177
54
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
pages with no dev server at all, a small dev-only server of its own with that overlay; the in-app agent (`genaicode/app-agent` with
|
|
183
|
-
`<AppAgentChat>`) when the agent should act on the app itself; and `npx genaicode ui` next
|
|
184
|
-
to the dev server when there is no Node process to host it. Words after `embed` go to the
|
|
185
|
-
agent as your request. You watch and answer in the same chat as `genaicode chat` (`--ui`
|
|
186
|
-
for the browser); without a terminal it runs headless like `run`. The agent installs the
|
|
187
|
-
package, edits the config, runs your checks and reports; it does not commit.
|
|
188
|
-
|
|
189
|
-
The instructions are also a Claude Code skill. Copy
|
|
190
|
-
[`skills/genaicode-embed`](skills/genaicode-embed/SKILL.md) into `.claude/skills/` (or
|
|
191
|
-
`~/.claude/skills/`) and ask Claude to "embed genaicode":
|
|
192
|
-
|
|
193
|
-
```bash
|
|
194
|
-
mkdir -p .claude/skills && cp -r node_modules/genaicode/skills/genaicode-embed .claude/skills/
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Inside a Vite app
|
|
198
|
-
|
|
199
|
-
```ts
|
|
200
|
-
// vite.config.ts
|
|
201
|
-
import { defineConfig } from 'vite';
|
|
202
|
-
import genaicode from 'genaicode/vite';
|
|
203
|
-
|
|
204
|
-
export default defineConfig({ plugins: [genaicode()] });
|
|
205
|
-
```
|
|
55
|
+
Every driver gives the same events and result. On top: steering a running task, approvals
|
|
56
|
+
and sandbox policy (`permissions`, or `'yolo'`), per-task MCP servers, and verify-and-repair.
|
|
57
|
+
The agent keeps its own login, billing and sandbox; GenAIcode picks no model and adds no
|
|
58
|
+
agent loop of its own.
|
|
206
59
|
|
|
207
60
|
<p align="center">
|
|
208
61
|
<picture>
|
|
@@ -212,687 +65,18 @@ export default defineConfig({ plugins: [genaicode()] });
|
|
|
212
65
|
</picture>
|
|
213
66
|
</p>
|
|
214
67
|
|
|
215
|
-
|
|
216
|
-
in a panel, with the agent working in the project root. The wolf counts the page's errors
|
|
217
|
-
(build errors, uncaught exceptions, unhandled rejections, `console.error`), and "Fix N
|
|
218
|
-
errors" sends them to the agent as one prompt. Options: `agent`, `model`, `effort`,
|
|
219
|
-
`approveAll`, `agents`, `port`, and `captureErrors: false` to leave the page's errors alone.
|
|
220
|
-
Builds are untouched. The chat runs on its own port on 127.0.0.1 even when Vite runs with
|
|
221
|
-
`--host`, lets only the app's origins frame it, and hands its token (and takes "fix" requests)
|
|
222
|
-
only from the app's own pages on this machine.
|
|
223
|
-
|
|
224
|
-
Any other Node dev server (Express, webpack-dev-server, a script that serves static files)
|
|
225
|
-
gets the same overlay from `createDevOverlay` in `genaicode/ui`:
|
|
226
|
-
|
|
227
|
-
```ts
|
|
228
|
-
import { createDevOverlay } from 'genaicode/ui';
|
|
229
|
-
|
|
230
|
-
const overlay = createDevOverlay(); // same options as the plugin, plus `cwd`
|
|
231
|
-
app.use(overlay.middleware); // Express; or `if (await overlay.handle(req, res)) return;`
|
|
232
|
-
// ...and in the HTML it serves: overlay.injectHtml(html), or the overlay.script tag.
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
Development only. The token goes only to pages opened at `localhost`, `*.localhost`,
|
|
236
|
-
`127.0.0.1` or `[::1]`, and without an agent CLI the overlay stays off.
|
|
237
|
-
|
|
238
|
-
### In your own React app
|
|
239
|
-
|
|
240
|
-
`genaicode/react` is the same chat as a component. It draws a `SessionState` your app gives
|
|
241
|
-
it and calls you back; it runs, fetches and stores nothing, so your server owns the session,
|
|
242
|
-
its MCP servers and its prompts. React 18 or newer comes from your app: genaicode does not
|
|
243
|
-
declare or install it.
|
|
244
|
-
|
|
245
|
-
```ts
|
|
246
|
-
// server (Node): one session, streamed to the page
|
|
247
|
-
import { createAgentSession, claude } from 'genaicode/agents';
|
|
248
|
-
|
|
249
|
-
const session = createAgentSession({
|
|
250
|
-
agent: claude(),
|
|
251
|
-
cwd: process.cwd(),
|
|
252
|
-
task: { mcpServers: [{ name: 'ops', url: 'http://127.0.0.1:4000/mcp' }] },
|
|
253
|
-
// The agent gets the context; the transcript keeps what the user typed.
|
|
254
|
-
transformPrompt: (text) => `${briefing}\n\nOperator message:\n${text}`,
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
app.get('/api/agent/events', (req, res) => {
|
|
258
|
-
res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-store' });
|
|
259
|
-
const push = (state) => res.write(`data: ${JSON.stringify(state)}\n\n`);
|
|
260
|
-
push(session.get());
|
|
261
|
-
res.on('close', session.subscribe(push));
|
|
262
|
-
});
|
|
263
|
-
app.post('/api/agent/send', (req, res) => {
|
|
264
|
-
session.send(req.body.text);
|
|
265
|
-
res.json({ ok: true });
|
|
266
|
-
});
|
|
267
|
-
app.post('/api/agent/stop', (req, res) => {
|
|
268
|
-
session.stop();
|
|
269
|
-
res.json({ ok: true });
|
|
270
|
-
});
|
|
271
|
-
app.post('/api/agent/approve', (req, res) => res.json({ ok: session.approve(req.body.id, req.body.decision) }));
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
```tsx
|
|
275
|
-
// page
|
|
276
|
-
import { useEffect, useState } from 'react';
|
|
277
|
-
import { AgentChat, type SessionState } from 'genaicode/react';
|
|
278
|
-
import 'genaicode/react/styles.css';
|
|
279
|
-
|
|
280
|
-
const post = (path: string, body: object = {}) =>
|
|
281
|
-
fetch(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
|
|
282
|
-
|
|
283
|
-
export function AgentDrawer() {
|
|
284
|
-
const [state, setState] = useState<SessionState | null>(null);
|
|
285
|
-
useEffect(() => {
|
|
286
|
-
const events = new EventSource('/api/agent/events');
|
|
287
|
-
events.onmessage = (event) => setState(JSON.parse(event.data));
|
|
288
|
-
return () => events.close();
|
|
289
|
-
}, []);
|
|
290
|
-
return (
|
|
291
|
-
<AgentChat
|
|
292
|
-
state={state}
|
|
293
|
-
onSend={(text) => post('/api/agent/send', { text })}
|
|
294
|
-
onStop={() => post('/api/agent/stop')}
|
|
295
|
-
onApprove={(id, decision) => post('/api/agent/approve', { id, decision })}
|
|
296
|
-
formatToolName={(name) => name.replace(/^mcp__ops__/, '')}
|
|
297
|
-
renderFooter={() => <MyApprovalCards />}
|
|
298
|
-
headerExtras={<button onClick={() => post('/api/agent/reset')}>New</button>}
|
|
299
|
-
/>
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
`<AgentChat>` fills its container's height. It shows turns with streaming Markdown (no raw
|
|
305
|
-
HTML; links open in a new tab), collapsible tool calls, edited files, errors, the agent's
|
|
306
|
-
own approval requests, queued prompts and a working indicator, and an input where Enter
|
|
307
|
-
sends, Shift+Enter adds a line and Esc stops. Props:
|
|
308
|
-
|
|
309
|
-
| Prop | |
|
|
310
|
-
| ------------------------------------------------------------------------------ | ---------------------------------------------------------------- |
|
|
311
|
-
| `state` | `SessionState`, or null before there is a session |
|
|
312
|
-
| `onSend(text)`, `onStop()` | Enter or Send; Stop or Esc. No `onStop`, no Stop button |
|
|
313
|
-
| `onApprove(id, decision)` | answers `state.approvals`; without it they show as waiting |
|
|
314
|
-
| `renderPrompt(text, turn?)` | how a sent prompt shows (return null to hide it) |
|
|
315
|
-
| `renderAfterTurn(turn, i)`, `renderFooter()` | your content after a turn, or at the end |
|
|
316
|
-
| `notices` | `{ id, afterTurn, text, tone? }[]`: your own lines between turns |
|
|
317
|
-
| `foldSteps` | fold each turn's tools and edits into one "N steps" line |
|
|
318
|
-
| `header`, `headerExtras` | replace the header (null for none), or add to its right end |
|
|
319
|
-
| `emptyState`, `placeholder`, `hint` | text before the first turn, in and under the input |
|
|
320
|
-
| `formatToolName(name)`, `disabled`, `autoFocus`, `theme`, `className`, `style` | |
|
|
321
|
-
|
|
322
|
-
Theme it with CSS custom properties on the component or any ancestor: `--gc-bg`,
|
|
323
|
-
`--gc-panel`, `--gc-border`, `--gc-text`, `--gc-muted`, `--gc-accent`, `--gc-accent-text`,
|
|
324
|
-
`--gc-error`, `--gc-font`, `--gc-mono`, `--gc-font-size`, `--gc-radius`, `--gc-max-width`
|
|
325
|
-
(and `--gc-ok`, `--gc-warn`, plus soft shades mixed from these unless set). Unset ones keep
|
|
326
|
-
the `genaicode ui` look, light or dark by `prefers-color-scheme`, or by `theme="light"` /
|
|
327
|
-
`"dark"`. `genaicode ui` and the Vite panel draw this same component.
|
|
328
|
-
|
|
329
|
-
Two smaller pieces come along for hosts that build their own panels around a transcript:
|
|
330
|
-
|
|
331
|
-
```ts
|
|
332
|
-
import { installResizeHandle, storedSize, useFollowScroll } from 'genaicode/react';
|
|
333
|
-
|
|
334
|
-
// A log that stays at its newest line until the user scrolls up (and again after follow()).
|
|
335
|
-
const { ref, onScroll, follow } = useFollowScroll(lines);
|
|
336
|
-
|
|
337
|
-
// A drag handle on a panel's left edge; the width survives reloads.
|
|
338
|
-
const stored = storedSize('panel-width', [300, 900]);
|
|
339
|
-
let width = stored.get();
|
|
340
|
-
const handle = installResizeHandle(edge, {
|
|
341
|
-
axis: 'x',
|
|
342
|
-
direction: () => -1, // dragging left makes the panel wider
|
|
343
|
-
bounds: () => [300, Math.min(900, innerWidth - 24)],
|
|
344
|
-
read: () => width ?? 390,
|
|
345
|
-
write: (next, commit) => {
|
|
346
|
-
width = next;
|
|
347
|
-
panel.style.setProperty('--width', `${width}px`);
|
|
348
|
-
if (commit) stored.set(width);
|
|
349
|
-
},
|
|
350
|
-
reset: () => {
|
|
351
|
-
width = undefined;
|
|
352
|
-
panel.style.removeProperty('--width');
|
|
353
|
-
stored.clear();
|
|
354
|
-
},
|
|
355
|
-
});
|
|
356
|
-
// The CSS caps the shown width, so the saved one comes back when the window grows again:
|
|
357
|
-
// .panel { width: min(var(--width, 390px), calc(100vw - 24px)); }
|
|
358
|
-
window.addEventListener('resize', handle.sync);
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
The handle keeps its `aria-value*` range current and takes the arrow keys (Shift for bigger
|
|
362
|
-
steps), Home and End, Escape to undo a drag, and Enter or double-click to reset. While a drag
|
|
363
|
-
runs, `document.body` (or `root`) has `data-resizing="true"`, so the page can keep the cursor
|
|
364
|
-
and turn off pointer events on iframes. `read()` must return what `write` or `reset` just set,
|
|
365
|
-
so keep the size in a variable or a ref rather than only in React state.
|
|
366
|
-
|
|
367
|
-
To show the whole `genaicode ui` page in a frame instead, `startEmbeddedWeb` from
|
|
368
|
-
`genaicode/ui` serves it on 127.0.0.1 with your `task` (`mcpServers`, `timeoutMs`...), `env`
|
|
369
|
-
and `transformPrompt`, and returns its tokenized `url` and a `controller` to send prompts.
|
|
370
|
-
|
|
371
|
-
### Inside your own local app
|
|
372
|
-
|
|
373
|
-
`genaicode/app-agent` puts an agent in a local app you run on your machine (an admin
|
|
374
|
-
console, an internal tool, a game editor). The user chats with an agent CLI they already
|
|
375
|
-
have; the agent works the app through tools the app serves over MCP, changes wait for the
|
|
376
|
-
user's approval, and in `dev` mode the agent also edits the app's code and proposes a commit.
|
|
377
|
-
|
|
378
|
-
```ts
|
|
379
|
-
import { createServer } from 'node:http';
|
|
380
|
-
import { createAppAgent, createAppAgentHandler } from 'genaicode/app-agent';
|
|
381
|
-
|
|
382
|
-
const agent = createAppAgent({
|
|
383
|
-
name: 'the shop admin',
|
|
384
|
-
mcpUrl: 'http://127.0.0.1:3000/api/agent/mcp',
|
|
385
|
-
tools: [
|
|
386
|
-
{ name: 'orders', description: 'List open orders.', run: () => JSON.stringify(listOrders()) },
|
|
387
|
-
{
|
|
388
|
-
name: 'refund',
|
|
389
|
-
description: 'Refund an order.',
|
|
390
|
-
write: true, // a card with Approve and Reject; runs only after Approve
|
|
391
|
-
inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
|
|
392
|
-
run: ({ id }) => refund(String(id)),
|
|
393
|
-
},
|
|
394
|
-
],
|
|
395
|
-
pages: () => ['/orders', '/customers'], // page context, link chips, "take me there"
|
|
396
|
-
dev: { cwd: process.cwd(), commands: ['npm test'] }, // optional: the agent edits the app
|
|
397
|
-
});
|
|
398
|
-
const handle = createAppAgentHandler(agent, { authorize: (req) => isSignedIn(req) });
|
|
399
|
-
|
|
400
|
-
createServer(async (req, res) => {
|
|
401
|
-
if (await handle(req, res)) return;
|
|
402
|
-
// ...the rest of the app
|
|
403
|
-
}).listen(3000, '127.0.0.1');
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
On the page, `AppAgentChat` from `genaicode/react` is the whole drawer: the chat, the agent's
|
|
407
|
-
page chips under its replies, Approve and Reject cards (with the diff of a proposed commit),
|
|
408
|
-
and the agent and mode pickers. It reports `currentPath` to the agent and calls `onNavigate`
|
|
409
|
-
when a chip is clicked or the agent is asked to go somewhere.
|
|
410
|
-
|
|
411
|
-
```tsx
|
|
412
|
-
import { AppAgentChat } from 'genaicode/react';
|
|
413
|
-
import 'genaicode/react/styles.css';
|
|
414
|
-
|
|
415
|
-
<AppAgentChat currentPath={location.pathname} onNavigate={(path) => router.push(path)} />;
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
<p align="center">
|
|
419
|
-
<img alt="A shop admin with the agent drawer: the agent proposed a refund, waiting for Approve." src="docs/media/screenshots/app-agent.png" width="80%">
|
|
420
|
-
</p>
|
|
421
|
-
|
|
422
|
-
Under it, `/api/agent` serves `GET /events` (a stream of `AppAgentState`: the session, earlier
|
|
423
|
-
turns, pending actions, link chips, navigation requests) and `POST /send`, `/stop`, `/reset`,
|
|
424
|
-
`/config`, `/view`, `/actions/:id` and `/approvals/:id` (the agent CLI's own permission
|
|
425
|
-
prompts); `useAppAgent` wraps them for a page of your own. Switching agent or mode keeps the
|
|
426
|
-
conversation: the next agent gets a short transcript. `trace` receives every prompt, MCP call,
|
|
427
|
-
decision and finished turn, for a log. Default agents are Claude Code, Codex and
|
|
428
|
-
Copilot CLI, locked down per mode: in `use` mode they get only the app's tools (no shell, no
|
|
429
|
-
file edits); in `dev` mode they edit files and run the listed commands, never commit or push (Claude and
|
|
430
|
-
Copilot enforce the command list; Codex has no per-command allowlist, so it runs in its
|
|
431
|
-
`workspace-write` sandbox: writes only inside `cwd`, no network).
|
|
432
|
-
`propose_commit` snapshots the worktree, the user reviews that diff, and Approve commits
|
|
433
|
-
exactly that tree on the reviewed HEAD, or refuses if the files or HEAD changed since, a hook
|
|
434
|
-
changed the files, or a merge or rebase is in progress. The repository's commit hooks run.
|
|
435
|
-
The agent sees no MCP server but the app's: Claude runs with `--strict-mcp-config`, and Codex
|
|
436
|
-
and Copilot get the user's own configured servers switched off. The MCP endpoint takes a per-run
|
|
437
|
-
bearer and refuses browser requests; every other route asks `authorize` and refuses
|
|
438
|
-
cross-site requests. Without `authorize` only a loopback `Host` is accepted (add names with
|
|
439
|
-
`allowedHosts`), so a DNS-rebinding page cannot reach the agent.
|
|
440
|
-
|
|
441
|
-
## Also: a thin LLM layer
|
|
442
|
-
|
|
443
|
-
Besides driving agents, GenAIcode is a small TypeScript toolkit for calling LLMs from backend
|
|
444
|
-
code. It sits between raw provider SDKs and full agent frameworks: one prompt representation,
|
|
445
|
-
thin provider adapters, a convenient request API, and lightweight conversation chains. This
|
|
446
|
-
layer does not inspect repositories, execute shell commands, edit files, or run an agent UI.
|
|
447
|
-
|
|
448
|
-
**Like jQuery**, the common case starts with one small function and becomes more specific
|
|
449
|
-
through chaining—configure a request, follow up across multiple prompts, and keep history
|
|
450
|
-
portable without adopting a full agent framework.
|
|
451
|
-
|
|
452
|
-
### A prompt in three lines
|
|
453
|
-
|
|
454
|
-
```ts
|
|
455
|
-
import { genaicode } from 'genaicode';
|
|
456
|
-
import { openai } from 'genaicode/providers';
|
|
457
|
-
|
|
458
|
-
const ai = genaicode(openai({ model: 'your-model-name' }));
|
|
459
|
-
const answer = await ai('Explain why the sky is blue in two sentences.').text();
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
The client is callable on purpose. Configuration is ordinary method chaining—system
|
|
463
|
-
instructions, temperature, token limits, and more—without a separate options object or
|
|
464
|
-
framework setup:
|
|
465
|
-
|
|
466
|
-
```ts
|
|
467
|
-
const result = await ai('Create a release note from these commits')
|
|
468
|
-
.system('You are a concise technical writer.')
|
|
469
|
-
.temperature(0.2)
|
|
470
|
-
.maxOutputTokens(500)
|
|
471
|
-
.text();
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
Builders are immutable, so a configured base request can be safely reused:
|
|
475
|
-
|
|
476
|
-
```ts
|
|
477
|
-
const releaseNote = ai('Create a release note from these commits')
|
|
478
|
-
.system('You are a concise technical writer.')
|
|
479
|
-
.temperature(0.2);
|
|
480
|
-
|
|
481
|
-
const short = await releaseNote.maxOutputTokens(200).text();
|
|
482
|
-
const long = await releaseNote.maxOutputTokens(800).text();
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
### Why this vs a raw SDK
|
|
486
|
-
|
|
487
|
-
Provider SDKs are the right tool when you call one vendor and own every request shape
|
|
488
|
-
yourself. The cost shows up when the same backend needs a second provider, a test double,
|
|
489
|
-
or shared policy (timeouts, JSON parsing, rate limits, fallback) without copying glue.
|
|
490
|
-
|
|
491
|
-
GenAIcode is that thin shared layer:
|
|
492
|
-
|
|
493
|
-
- One portable prompt/tool IR (`PromptItem`) instead of OpenAI messages vs Anthropic
|
|
494
|
-
blocks vs Gemini contents.
|
|
495
|
-
- One request API (`.text()`, `.json()`, `.stream()`, chains) over those adapters.
|
|
496
|
-
- A tiny `ModelProvider` seam so tests and custom gateways do not mock vendor HTTP.
|
|
497
|
-
- Opt-in middleware and retry helpers—no hidden tool runners, no silent retries, no
|
|
498
|
-
agent loop.
|
|
499
|
-
|
|
500
|
-
If you are happy importing one SDK and never swapping models or providers, stay on the
|
|
501
|
-
SDK. If you want the call site to look the same while the edge stays replaceable, use
|
|
502
|
-
GenAIcode.
|
|
503
|
-
|
|
504
|
-
### Chaining prompts
|
|
505
|
-
|
|
506
|
-
A chain remembers successful user and assistant turns. Each new prompt sees the complete
|
|
507
|
-
history, so multi-step work stays in ordinary application code:
|
|
508
|
-
|
|
509
|
-
```ts
|
|
510
|
-
import { system } from 'genaicode';
|
|
511
|
-
|
|
512
|
-
const chain = ai.chain(system('You are helping refine an API design.'));
|
|
513
|
-
|
|
514
|
-
const answer1 = await chain.text('Prompt 1: propose a minimal endpoint.');
|
|
515
|
-
const answer2 = await chain.text('Prompt 2: add idempotency to that design.');
|
|
516
|
-
const answer3 = await chain.text('Prompt 3: summarize the final contract.');
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
This is `prompt1 → response1 → prompt2 → response2 → prompt3 → response3`. The history
|
|
520
|
-
is available as portable `PromptItem[]` through `chain.history()`.
|
|
521
|
-
|
|
522
|
-
### Using a chain in a loop
|
|
523
|
-
|
|
524
|
-
A chain is ordinary application state, so normal control flow works:
|
|
525
|
-
|
|
526
|
-
```ts
|
|
527
|
-
const chain = ai.chain(system('Improve the draft while preserving its meaning.'));
|
|
528
|
-
let draft = 'The deployment had a problem and we fixed it.';
|
|
529
|
-
|
|
530
|
-
for (const instruction of ['Make it specific.', 'Make it concise.', 'Use a professional tone.']) {
|
|
531
|
-
draft = await chain.text(`${instruction}\n\nCurrent draft:\n${draft}`);
|
|
532
|
-
}
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
Calls on one chain are serialized, even if application code starts them concurrently.
|
|
536
|
-
Failed provider calls are not added to history.
|
|
537
|
-
|
|
538
|
-
Validation and repair are also plain loops rather than a special agent abstraction:
|
|
539
|
-
|
|
540
|
-
```ts
|
|
541
|
-
import { parseJsonResult, resultText, system } from 'genaicode';
|
|
542
|
-
|
|
543
|
-
const repair = ai.chain(system('Return only valid JSON with a non-negative count.'));
|
|
544
|
-
let count: number | undefined;
|
|
545
|
-
|
|
546
|
-
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
547
|
-
const result = await repair.ask(
|
|
548
|
-
attempt === 1 ? 'Count the actionable items in this text: ...' : 'Correct the previous response.',
|
|
549
|
-
);
|
|
550
|
-
|
|
551
|
-
try {
|
|
552
|
-
const value = parseJsonResult<{ count: number }>(result);
|
|
553
|
-
if (value.count < 0) throw new Error('count must be non-negative');
|
|
554
|
-
count = value.count;
|
|
555
|
-
break;
|
|
556
|
-
} catch (error) {
|
|
557
|
-
if (attempt === 3) throw error;
|
|
558
|
-
console.warn('Invalid model response:', resultText(result));
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
The application owns validation, attempt limits, and failure policy.
|
|
564
|
-
|
|
565
|
-
### Schema adapters
|
|
566
|
-
|
|
567
|
-
`json(...)` and `parseJsonResult(...)` accept either a parser function or a schema adapter
|
|
568
|
-
with a `parse(value)` method. This keeps schema validation library-agnostic:
|
|
569
|
-
|
|
570
|
-
```ts
|
|
571
|
-
const value = await ai('Return {"count": 3}.').json({
|
|
572
|
-
parse(input) {
|
|
573
|
-
if (typeof input !== 'object' || input === null || typeof input.count !== 'number') {
|
|
574
|
-
throw new Error('Invalid shape');
|
|
575
|
-
}
|
|
576
|
-
return input;
|
|
577
|
-
},
|
|
578
|
-
});
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
Calling `.json()` also sets `responseFormat: { type: 'json' }` on the request when you have
|
|
582
|
-
not already chosen a format, so providers that support JSON mode (OpenAI, Gemini/Vertex)
|
|
583
|
-
are asked for JSON rather than free text.
|
|
584
|
-
|
|
585
|
-
### Response format and thinking
|
|
586
|
-
|
|
587
|
-
Portable request fields cover the two knobs backends usually poke through provider-specific
|
|
588
|
-
config:
|
|
589
|
-
|
|
590
|
-
```ts
|
|
591
|
-
const verdict = await ai(promptText)
|
|
592
|
-
.responseFormat({ type: 'json' })
|
|
593
|
-
.thinking({ level: 'minimal' }) // or { budgetTokens: 0 } / false to disable
|
|
594
|
-
.temperature(0)
|
|
595
|
-
.json((value) => VerdictSchema.parse(value));
|
|
596
|
-
```
|
|
597
|
-
|
|
598
|
-
- `responseFormat`: `{ type: 'text' | 'json' }` or
|
|
599
|
-
`{ type: 'json_schema', name, schema, strict? }`.
|
|
600
|
-
- `thinking`: `false` to disable, or `{ budgetTokens?, level? }` (`minimal` |
|
|
601
|
-
`low` | `medium` | `high`). Prefer one of budget or level — some providers reject both.
|
|
602
|
-
On Google/Gemini, `false` and `budgetTokens: 0` map to `thinkingLevel: MINIMAL`
|
|
603
|
-
(or `LOW` for models like Gemini 3.7 and Gemini 3 Pro where `MINIMAL` is unsupported)
|
|
604
|
-
because Gemini 3 rejects `thinkingBudget: 0`. JSON `responseFormat` without an
|
|
605
|
-
explicit `thinking` setting or provider `generationConfig.thinkingConfig` default also
|
|
606
|
-
defaults Google thinking to `MINIMAL` (or `LOW` on Gemini 3.7 / Pro).
|
|
607
|
-
On Anthropic, `level` becomes `output_config.effort` with adaptive thinking (Opus and
|
|
608
|
-
Sonnet 4.6+), and `false` uses whatever the model accepts: `disabled`, `between_tools`
|
|
609
|
-
(Sonnet 5.5), or effort `low` where thinking is always on (Opus 5.5, Fable). Budgets
|
|
610
|
-
become adaptive thinking on models that removed them.
|
|
611
|
-
|
|
612
|
-
Providers map what they support and ignore the rest. `ProviderCapabilities.jsonResponse`
|
|
613
|
-
and `ProviderCapabilities.thinking` advertise support. Vendor-specific escapes such as
|
|
614
|
-
Vertex `generationConfig` remain available for anything not covered here.
|
|
615
|
-
|
|
616
|
-
### PromptItem: the portable prompt IR
|
|
617
|
-
|
|
618
|
-
`PromptItem` is GenAIcode's provider-neutral intermediate representation:
|
|
619
|
-
|
|
620
|
-
```ts
|
|
621
|
-
import { image, prompt, system, toolResults, user } from 'genaicode';
|
|
622
|
-
|
|
623
|
-
const items = prompt(
|
|
624
|
-
system('Return a one-sentence caption.'),
|
|
625
|
-
user('Describe this image.', {
|
|
626
|
-
images: [image(base64Png, 'image/png')],
|
|
627
|
-
}),
|
|
628
|
-
toolResults({
|
|
629
|
-
callId: 'lookup-1',
|
|
630
|
-
name: 'lookup',
|
|
631
|
-
content: JSON.stringify({ locale: 'pl-PL' }),
|
|
632
|
-
}),
|
|
633
|
-
);
|
|
634
|
-
|
|
635
|
-
const caption = await ai(items).text();
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
Applications can make domain objects prompt-aware without coupling them to a provider:
|
|
639
|
-
|
|
640
|
-
```ts
|
|
641
|
-
import { asPrompt, system, user } from 'genaicode';
|
|
642
|
-
|
|
643
|
-
const supportTicket = (ticket: Ticket) =>
|
|
644
|
-
asPrompt(() => [system('Triage support tickets.'), user(JSON.stringify(ticket))]);
|
|
645
|
-
|
|
646
|
-
await ai(supportTicket(ticket)).text();
|
|
647
|
-
```
|
|
648
|
-
|
|
649
|
-
### Tools
|
|
650
|
-
|
|
651
|
-
```ts
|
|
652
|
-
const calls = await ai('What is the weather in Warsaw?')
|
|
653
|
-
.tools([
|
|
654
|
-
{
|
|
655
|
-
name: 'weather',
|
|
656
|
-
description: 'Get current weather',
|
|
657
|
-
parameters: {
|
|
658
|
-
type: 'object',
|
|
659
|
-
properties: { city: { type: 'string' } },
|
|
660
|
-
required: ['city'],
|
|
661
|
-
additionalProperties: false,
|
|
662
|
-
},
|
|
663
|
-
},
|
|
664
|
-
])
|
|
665
|
-
.toolCalls();
|
|
666
|
-
```
|
|
667
|
-
|
|
668
|
-
GenAIcode normalizes tool calls but deliberately does not execute them. The application
|
|
669
|
-
owns permissions, retries, and side effects.
|
|
670
|
-
|
|
671
|
-
### Streaming
|
|
672
|
-
|
|
673
|
-
Providers that support native streaming expose a provider-neutral `StreamEvent` IR.
|
|
674
|
-
Request builders and chains offer `.stream()` and `.streamText()`. If a provider has no
|
|
675
|
-
`stream` method, GenAIcode synthesizes a short stream from `generate`.
|
|
676
|
-
|
|
677
|
-
```ts
|
|
678
|
-
for await (const event of ai('Write a haiku about queues.').stream()) {
|
|
679
|
-
if (event.type === 'text-delta') process.stdout.write(event.text);
|
|
680
|
-
if (event.type === 'done') console.log('\n', event.result.usage);
|
|
681
|
-
}
|
|
682
|
-
```
|
|
683
|
-
|
|
684
|
-
Built-in adapters declare capability metadata:
|
|
685
|
-
|
|
686
|
-
```ts
|
|
687
|
-
ai.provider.capabilities;
|
|
688
|
-
// { streaming: true, tools: true, images: 'input', systemPrompt: true }
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
### Middleware
|
|
692
|
-
|
|
693
|
-
Hook-style plugins remain the extension point. Built-in helpers cover common backend
|
|
694
|
-
needs without restoring a global registry:
|
|
695
|
-
|
|
696
|
-
```ts
|
|
697
|
-
import { cachePlugin, fallbackPlugin, genaicode, rateLimitPlugin, timingPlugin } from 'genaicode';
|
|
698
|
-
import { anthropic, openai } from 'genaicode/providers';
|
|
699
|
-
|
|
700
|
-
const ai = genaicode(openai({ model: 'your-model-name' }), {
|
|
701
|
-
plugins: [
|
|
702
|
-
timingPlugin(),
|
|
703
|
-
rateLimitPlugin({ concurrency: 2, minIntervalMs: 50 }),
|
|
704
|
-
cachePlugin({ maxEntries: 64 }),
|
|
705
|
-
fallbackPlugin({ providers: [anthropic({ model: 'your-claude-model' })] }),
|
|
706
|
-
],
|
|
707
|
-
});
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
### Retries
|
|
711
|
-
|
|
712
|
-
Retries stay in application code. Use `classifyError`, `isRetryable`, and `withRetry`
|
|
713
|
-
when you want shared classification without hidden policy:
|
|
714
|
-
|
|
715
|
-
```ts
|
|
716
|
-
import { withRetry } from 'genaicode';
|
|
717
|
-
|
|
718
|
-
const text = await withRetry(() => ai('Summarize the deploy notes.').text(), {
|
|
719
|
-
attempts: 3,
|
|
720
|
-
delayMs: 250,
|
|
721
|
-
});
|
|
722
|
-
```
|
|
723
|
-
|
|
724
|
-
See [retry guidance](docs/retry.md), [semver policy](docs/semver.md), and the
|
|
725
|
-
[provider package evaluation](docs/provider-packages.md).
|
|
726
|
-
|
|
727
|
-
### Providers
|
|
728
|
-
|
|
729
|
-
Models are provided in the adapter, through `.model(...)`, or with environment variables:
|
|
730
|
-
|
|
731
|
-
| Adapter | Factory | Environment defaults |
|
|
732
|
-
| ---------------------- | -------------------- | ------------------------------------------------------------------ |
|
|
733
|
-
| OpenAI | `openai()` | `OPENAI_API_KEY`, `OPENAI_MODEL` |
|
|
734
|
-
| Anthropic | `anthropic()` | `ANTHROPIC_API_KEY`, `ANTHROPIC_MODEL` |
|
|
735
|
-
| Gemini API / AI Studio | `gemini()` | `GEMINI_API_KEY` or `API_KEY`, `GEMINI_MODEL` |
|
|
736
|
-
| Vertex AI | `vertexAI()` | `GOOGLE_CLOUD_PROJECT`, `GOOGLE_CLOUD_LOCATION`, `VERTEX_AI_MODEL` |
|
|
737
|
-
| OpenAI-compatible | `openaiCompatible()` | Explicit options; OpenAI defaults also work |
|
|
738
|
-
|
|
739
|
-
```ts
|
|
740
|
-
import { anthropic, gemini, openai, vertexAI } from 'genaicode/providers';
|
|
741
|
-
|
|
742
|
-
const viaOpenAI = genaicode(openai({ model: 'your-openai-model' }));
|
|
743
|
-
const viaClaude = genaicode(anthropic({ model: 'your-claude-model' }));
|
|
744
|
-
const viaGemini = genaicode(gemini({ model: 'your-gemini-model' }));
|
|
745
|
-
const viaVertex = genaicode(
|
|
746
|
-
vertexAI({
|
|
747
|
-
project: 'my-google-cloud-project',
|
|
748
|
-
location: 'global',
|
|
749
|
-
model: 'your-vertex-model',
|
|
750
|
-
}),
|
|
751
|
-
);
|
|
752
|
-
```
|
|
753
|
-
|
|
754
|
-
Provider-specific options stay at provider construction. For example, Anthropic exposes
|
|
755
|
-
thinking and output-token defaults, while Gemini and Vertex expose SDK HTTP, auth, API
|
|
756
|
-
version, and generation configuration options.
|
|
757
|
-
|
|
758
|
-
GitHub Models, Ollama, vLLM, and other compatible gateways use the OpenAI-compatible
|
|
759
|
-
adapter:
|
|
760
|
-
|
|
761
|
-
```ts
|
|
762
|
-
import { openaiCompatible } from 'genaicode/providers';
|
|
763
|
-
|
|
764
|
-
const local = genaicode(
|
|
765
|
-
openaiCompatible({
|
|
766
|
-
name: 'local',
|
|
767
|
-
baseURL: 'http://localhost:11434/v1',
|
|
768
|
-
apiKey: 'local',
|
|
769
|
-
model: 'qwen3',
|
|
770
|
-
}),
|
|
771
|
-
);
|
|
772
|
-
```
|
|
773
|
-
|
|
774
|
-
Implementing a provider directly is intentionally small:
|
|
775
|
-
|
|
776
|
-
```ts
|
|
777
|
-
import type { ModelProvider } from 'genaicode';
|
|
778
|
-
|
|
779
|
-
const provider: ModelProvider = {
|
|
780
|
-
name: 'internal-gateway',
|
|
781
|
-
async generate(request) {
|
|
782
|
-
// Convert request.prompt to your API and normalize the response.
|
|
783
|
-
return { parts: [{ type: 'text', text: 'response' }] };
|
|
784
|
-
},
|
|
785
|
-
};
|
|
786
|
-
```
|
|
787
|
-
|
|
788
|
-
All Anthropic, Google, and OpenAI conversion functions are public from
|
|
789
|
-
`genaicode/providers`, so gateways and tests can reuse them without instantiating clients.
|
|
790
|
-
|
|
791
|
-
### Provider E2E tests
|
|
792
|
-
|
|
793
|
-
Run real-provider E2E tests locally with:
|
|
68
|
+
## Install
|
|
794
69
|
|
|
795
70
|
```bash
|
|
796
|
-
npm
|
|
797
|
-
```
|
|
798
|
-
|
|
799
|
-
These tests are credential-gated and run only when provider-specific environment variables
|
|
800
|
-
are set:
|
|
801
|
-
|
|
802
|
-
- OpenAI: `OPENAI_API_KEY`, `OPENAI_MODEL`
|
|
803
|
-
- Anthropic: `ANTHROPIC_API_KEY`, `ANTHROPIC_MODEL`
|
|
804
|
-
- Gemini: `GEMINI_API_KEY`, `GEMINI_MODEL`
|
|
805
|
-
|
|
806
|
-
Beyond the smoke call, E2E also covers portable `responseFormat: { type: 'json' }`
|
|
807
|
-
(OpenAI, Gemini) and `thinking` (Anthropic disable; Gemini disable / `level: 'minimal'`).
|
|
808
|
-
|
|
809
|
-
CI/CD is configured in `.github/workflows/provider-e2e.yaml`. Each provider runs in its own
|
|
810
|
-
job and only starts when both required secrets are configured in GitHub Actions.
|
|
811
|
-
|
|
812
|
-
### Plugins and hooks
|
|
813
|
-
|
|
814
|
-
A provider plugin no longer needs registration in global configuration. It exports the
|
|
815
|
-
same small `ModelProvider` contract as a built-in adapter:
|
|
816
|
-
|
|
817
|
-
```ts
|
|
818
|
-
// @acme/genaicode-bedrock
|
|
819
|
-
import type { ModelProvider } from 'genaicode';
|
|
820
|
-
|
|
821
|
-
export const bedrock = (options: BedrockOptions): ModelProvider => ({
|
|
822
|
-
name: 'bedrock',
|
|
823
|
-
capabilities: { streaming: false, tools: true, systemPrompt: true },
|
|
824
|
-
async generate(request) {
|
|
825
|
-
// Convert PromptItem[], call Bedrock, and return GenerationResult.
|
|
826
|
-
return { parts: [{ type: 'text', text: 'response' }] };
|
|
827
|
-
},
|
|
828
|
-
});
|
|
829
|
-
```
|
|
830
|
-
|
|
831
|
-
Hook-style plugins use middleware. They can observe or rewrite requests and results,
|
|
832
|
-
handle errors, implement caches or fallbacks, and intentionally short-circuit a call.
|
|
833
|
-
Optional `stream` middleware follows the same registration order.
|
|
834
|
-
|
|
835
|
-
```ts
|
|
836
|
-
import { definePlugin, genaicode } from 'genaicode';
|
|
837
|
-
import { openai } from 'genaicode/providers';
|
|
838
|
-
|
|
839
|
-
const timing = definePlugin({
|
|
840
|
-
name: 'timing',
|
|
841
|
-
async generate(request, next) {
|
|
842
|
-
const startedAt = performance.now();
|
|
843
|
-
try {
|
|
844
|
-
return await next(request);
|
|
845
|
-
} finally {
|
|
846
|
-
console.log('LLM request took', performance.now() - startedAt, 'ms');
|
|
847
|
-
}
|
|
848
|
-
},
|
|
849
|
-
});
|
|
850
|
-
|
|
851
|
-
const ai = genaicode(openai({ model: 'your-model-name' }), {
|
|
852
|
-
plugins: [timing],
|
|
853
|
-
});
|
|
71
|
+
npm install genaicode
|
|
854
72
|
```
|
|
855
73
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
the 1.x runtime TypeScript loader or process-global plugin registry.
|
|
859
|
-
|
|
860
|
-
Framework-shaped examples live under [`docs/examples/`](docs/examples/) (`http-handler`, `queue-worker`,
|
|
861
|
-
`cron-job`).
|
|
862
|
-
|
|
863
|
-
## Design boundaries
|
|
864
|
-
|
|
865
|
-
- Backend library, not a coding agent. `genaicode/agents` drives external agent CLIs; it
|
|
866
|
-
adds no agent loop, prompts, or repository tools of its own.
|
|
867
|
-
- Provider-neutral core with no global configuration.
|
|
868
|
-
- Explicit models and credentials; environment variables are only provider defaults.
|
|
869
|
-
- Immutable request builders.
|
|
870
|
-
- Conversation history is explicit; loops remain ordinary application code.
|
|
871
|
-
- No hidden tool execution or hidden retries.
|
|
872
|
-
- Third-party providers and middleware use stable TypeScript contracts.
|
|
873
|
-
- Provider SDKs stay behind the `genaicode/providers` subpath; process spawning stays
|
|
874
|
-
behind `genaicode/agents`.
|
|
875
|
-
|
|
876
|
-
## Migration from 1.x
|
|
877
|
-
|
|
878
|
-
GenAIcode 1.x was a coding agent. Version 2.0 deliberately replaces that product with a
|
|
879
|
-
small backend LLM toolkit: a jQuery-like layer for portable prompts, provider adapters,
|
|
880
|
-
conversation chains, and plugins.
|
|
881
|
-
|
|
882
|
-
The 1.x coding agent, browser UI, repository tools, and shell execution are not
|
|
883
|
-
deprecated compatibility features; they have been removed from 2.0. The 2.x
|
|
884
|
-
`genaicode` command only drives external agent CLIs (see [From the command line](#from-the-command-line)). The GenAIcode name, `PromptItem` model, provider converters, and extensibility
|
|
885
|
-
continue here in a smaller and more focused form.
|
|
886
|
-
|
|
887
|
-
The original coding agent remains available from the preserved
|
|
888
|
-
[`1.x` branch](https://github.com/gtanczyk/genaicode/tree/1.x) and the 1.x npm releases:
|
|
889
|
-
|
|
890
|
-
```bash
|
|
891
|
-
npx genaicode@1
|
|
892
|
-
```
|
|
74
|
+
Importing `genaicode` never spawns an agent, and `genaicode/agents` loads no provider SDK
|
|
75
|
+
and no UI code.
|
|
893
76
|
|
|
894
|
-
|
|
895
|
-
(Claude Code, Codex, opencode...) instead of the 1.x built-in agent.
|
|
77
|
+
## More
|
|
896
78
|
|
|
897
|
-
|
|
898
|
-
|
|
79
|
+
- [Changelog](docs/CHANGELOG.md), [semver policy](docs/semver.md), [releasing](docs/releasing.md)
|
|
80
|
+
- Coming from 1.x? It was a built-in coding agent; it lives on the
|
|
81
|
+
[`1.x` branch](https://github.com/gtanczyk/genaicode/tree/1.x) and as `npx genaicode@1`.
|
|
82
|
+
See [the pivot plan](docs/pivot.md).
|