@agentiny/gemini
v0.2.0
Published
Google Gemini adapter for @agentiny/core
Readme
@agentiny/gemini
Google Gemini adapter for @agentiny/core. Turns a state-in / state-out generateContent call into an agent ActionFn.
Migration from 0.1.x
0.2.0 is a breaking change:
- The peer dependency is
@google/genai.@google/generative-aiis deprecated. createGeminiAction(config, options)is now a single options object.onResponsereceives a result object. Useresult.textfor the previous string.- The client is created once and reused. Pass
clientto share oneGoogleGenAIinstance. - The default model is still the
gemini-flash-latestalias.
// 0.1.x
createGeminiAction(
{ apiKey: process.env.GOOGLE_API_KEY! },
{
prompt: (state) => state.input,
onResponse: (text, state) => {
state.output = text;
},
},
);
// 0.2.0
createGeminiAction({
apiKey: process.env.GOOGLE_API_KEY,
prompt: (state) => state.input,
onResponse: (result, state) => {
state.output = result.text;
},
});Installation
npm install @agentiny/core @agentiny/gemini @google/genaiRequirements
- Node.js 20+ (22+ when
@google/genaireaches 3.x, which this package does not peer yet) - @agentiny/core (peer)
- @google/genai
>=1.9.0 <3(peer, install separately) - zod
^3.25 || ^4.0(optional peer, only if you pass a Zod schema)
Other adapters
Quick start
import { createGeminiAction } from '@agentiny/gemini';
import { Agent } from '@agentiny/core';
interface AnalysisState {
data: string;
analysis?: string;
}
const agent = new Agent<AnalysisState>({
initialState: { data: '' },
});
const analyzeAction = createGeminiAction({
apiKey: process.env.GOOGLE_API_KEY,
prompt: (state) => `Analyze this: ${state.data}`,
onResponse: (result, state) => {
state.analysis = result.text;
},
});
agent.addTrigger({
id: 'analyze-trigger',
check: (state) => !!state.data && !state.analysis,
actions: [analyzeAction],
repeat: false,
});
await agent.start();
agent.setState({ data: 'What is Gemini?' });API
createGeminiAction<TState, TParsed>(options)
Creates an ActionFn<TState> that calls Gemini models.generateContent.
The client is created once in the factory (or taken from options.client) and reused. When the agent passes an ActionContext, its abort signal is forwarded as config.abortSignal.
Client
client(optional) — ExistingGoogleGenAIinstance or compatible client. When set,apiKeyandbaseURLare ignored.apiKey(optional) — Used whenclientis omitted. The SDK also readsGOOGLE_API_KEY, thenGEMINI_API_KEY. If both are set, the SDK usesGOOGLE_API_KEY.baseURL(optional) — Custom endpoint. Sent ashttpOptions.baseUrl.model(optional) — Default:gemini-flash-latest.
Prompt (exactly one of prompt or messages)
prompt—(state) => stringturned into a single user content.messages—(state) => GeminiMessage[]for multi-turn history. Roles areuser,model, orassistant.assistantis sent asmodel.system—stringor(state) => string. Sent asconfig.systemInstruction, not as a message.
Result
onResponse—(result, state) => void | Promise<void>. Always receives:| Field | Meaning | | -------------- | ------------------------------------------------------------------------- | |
text| Concatenated text parts. Thought parts are skipped. Empty string if none. | |data|textwhen noschema; parsed value whenschemais set | |usage|{ promptTokens, completionTokens, totalTokens }fromusageMetadata| |finishReason| APIfinishReason(STOP,MAX_TOKENS, …), ornull| |model|modelVersionfrom the API, or the requested model | |raw| The response, or the last stream chunk |onDelta—(delta, state) => void | Promise<void>. Usesmodels.generateContentStream. Each non-empty text chunk is emitted;onResponsestill runs once at the end.
Generation
maxTokens— Sent asmaxOutputTokensonly when set. Gemini does not require it.temperature— Optional passthrough.schema— JSON Schema object, or a Zod-like object withsafeParse. Enables structured output;result.datais the parsed value.schemaName— Kept for adapter API parity.responseJsonSchemahas no name field, so this value is not sent.
Examples
Injected client
import { GoogleGenAI } from '@google/genai';
import { createGeminiAction } from '@agentiny/gemini';
const client = new GoogleGenAI({});
const action = createGeminiAction({
client,
prompt: (state) => state.input,
onResponse: (result, state) => {
state.output = result.text;
},
});System prompt and message history
History uses user and model. assistant is accepted and sent as model. The system prompt is a separate field:
const reply = createGeminiAction({
client,
system: 'You are terse.',
messages: (state) => state.history,
onResponse: (result, state) => {
state.history.push({ role: 'model', content: result.text });
},
});Temperature and max tokens
const creative = createGeminiAction({
apiKey: process.env.GOOGLE_API_KEY,
model: 'gemini-2.5-pro',
prompt: (state) => `Write a short story about: ${state.topic}`,
temperature: 1,
maxTokens: 500,
onResponse: (result, state) => {
state.story = result.text;
},
});On thinking models, maxOutputTokens counts thoughts as well as visible text. A very small cap can come back with an empty result.text.
Structured output
JSON Schema (sent as responseMimeType: 'application/json' and responseJsonSchema):
const extract = createGeminiAction({
prompt: (state) => state.email,
schema: {
type: 'object',
properties: {
tasks: { type: 'array', items: { type: 'string' } },
},
required: ['tasks'],
},
onResponse: (result, state) => {
state.tasks = result.data.tasks;
},
});Zod (optional peer — install zod yourself). Zod 4's toJSONSchema is sent as responseJsonSchema. Older Zod still requests JSON and validates with safeParse.
import { z } from 'zod';
const TaskList = z.object({
tasks: z.array(z.string()),
});
const extract = createGeminiAction({
prompt: (state) => state.email,
schema: TaskList,
onResponse: (result, state) => {
state.tasks = result.data.tasks;
},
});Streaming into state
const stream = createGeminiAction({
prompt: (state) => state.prompt,
onDelta: (delta, state) => {
state.draft += delta;
},
onResponse: (result, state) => {
state.draft = result.text;
},
});onDelta plus schema is allowed: deltas are the raw text (usually JSON), and result.data is parsed after the stream ends.
Chained triggers
interface ProcessState {
text: string;
summary?: string;
sentiment?: string;
}
const agent = new Agent<ProcessState>({
initialState: { text: 'Your text here' },
});
const client = new GoogleGenAI({});
const summarize = createGeminiAction({
client,
prompt: (state) => `Summarize: ${state.text}`,
onResponse: (result, state) => {
state.summary = result.text;
},
});
const analyzeSentiment = createGeminiAction({
client,
prompt: (state) => `Analyze sentiment of: ${state.summary}`,
onResponse: (result, state) => {
state.sentiment = result.text;
},
});
agent.addTrigger({
id: 'summarize-trigger',
check: (state) => !!state.text && !state.summary,
actions: [summarize],
});
agent.addTrigger({
id: 'analyze-trigger',
check: (state) => !!state.summary && !state.sentiment,
actions: [analyzeSentiment],
});Prefer one shared client when several actions hit the same account.
Custom endpoint
const custom = createGeminiAction({
apiKey: process.env.GEMINI_API_KEY,
baseURL: 'https://your-gemini-compatible-endpoint.example',
prompt: (state) => `Process: ${state.data}`,
onResponse: (result, state) => {
state.result = result.text;
},
});Errors and cancellation
API errors and abort errors propagate to the agent's onError. Long-running calls stop when the agent is paused or stopped because ctx.signal is forwarded.
const agent = new Agent<TextState>({
initialState: { input: '' },
onError: (error) => {
console.error('Agent error:', error.message);
},
});For retries, wrap the action with @agentiny/utils withRetry.
Best practices
- Keep API keys in the environment; pass a shared
clientwhen you have more than one action. - Use a current model alias (
gemini-flash-latest, or a newer flash/pro model) unless you need a pinned snapshot. - Set
maxTokenswhen you want a cap. Leave it unset when you want the model's default. - Handle failures with the agent's
onError. - Use
schemawhen the next trigger needs structured state, not a blob of prose.
Troubleshooting
"API key is missing or invalid"
Set GOOGLE_API_KEY or GEMINI_API_KEY, pass apiKey, or inject a client that already has credentials.
"Cannot find module '@google/genai'"
@google/genai is a peer dependency: npm install @google/genai. This package does not use @google/generative-ai.
"Model not found"
The default is gemini-flash-latest. Check that the name exists for your key, or set model.
Empty text with a small maxTokens
Thinking models can spend the output budget on thoughts. Raise maxTokens, or pick a model that is not a thinking model.
Rate limit exceeded
Wrap the action with withRetry from @agentiny/utils, or space calls out with a trigger delay.
License
MIT
