Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,18 @@ export default tseslint.config(
],
},
},
{
files: ['packages/sdk/src/**/*.{ts,tsx}'],
rules: {
'no-restricted-imports': [
'error',
{
name: '@google/gemini-cli-sdk',
message: 'Please use relative imports within the @google/gemini-cli-sdk package.',
},
],
},
},
{
files: ['packages/*/src/**/*.test.{ts,tsx}'],
plugins: {
Expand Down
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 2 additions & 15 deletions packages/cli/src/validateNonInterActiveAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,19 @@
* SPDX-License-Identifier: Apache-2.0
*/

import type { Config } from '@google/gemini-cli-core';
import type { Config, AuthType } from '@google/gemini-cli-core';
import {
AuthType,
debugLogger,
OutputFormat,
ExitCodes,
getAuthTypeFromEnv,
} from '@google/gemini-cli-core';
import { USER_SETTINGS_PATH } from './config/settings.js';
import { validateAuthMethod } from './config/auth.js';
import { type LoadedSettings } from './config/settings.js';
import { handleError } from './utils/errors.js';
import { runExitCleanup } from './utils/cleanup.js';

function getAuthTypeFromEnv(): AuthType | undefined {
if (process.env['GOOGLE_GENAI_USE_GCA'] === 'true') {
return AuthType.LOGIN_WITH_GOOGLE;
}
if (process.env['GOOGLE_GENAI_USE_VERTEXAI'] === 'true') {
return AuthType.USE_VERTEX_AI;
}
if (process.env['GEMINI_API_KEY']) {
return AuthType.USE_GEMINI;
}
return undefined;
}

export async function validateNonInteractiveAuth(
configuredAuthType: AuthType | undefined,
useExternalAuth: boolean | undefined,
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/core/contentGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,27 @@ export enum AuthType {
COMPUTE_ADC = 'compute-default-credentials',
}

/**
* Detects the best authentication type based on environment variables.
*
* Checks in order:
* 1. GOOGLE_GENAI_USE_GCA=true -> LOGIN_WITH_GOOGLE
* 2. GOOGLE_GENAI_USE_VERTEXAI=true -> USE_VERTEX_AI
* 3. GEMINI_API_KEY -> USE_GEMINI
*/
export function getAuthTypeFromEnv(): AuthType | undefined {
if (process.env['GOOGLE_GENAI_USE_GCA'] === 'true') {
return AuthType.LOGIN_WITH_GOOGLE;
}
if (process.env['GOOGLE_GENAI_USE_VERTEXAI'] === 'true') {
return AuthType.USE_VERTEX_AI;
}
if (process.env['GEMINI_API_KEY']) {
return AuthType.USE_GEMINI;
}
return undefined;
}

export type ContentGeneratorConfig = {
apiKey?: string;
vertexai?: boolean;
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export * from './prompts/mcp-prompts.js';
export * from './agents/types.js';
export * from './agents/agentLoader.js';
export * from './agents/local-executor.js';
export * from './agents/agent-scheduler.js';

// Export specific tool logic
export * from './tools/read-file.js';
Expand Down
36 changes: 36 additions & 0 deletions packages/sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# @google/gemini-cli-sdk

The Gemini CLI SDK provides a programmatic interface to interact with Gemini
models and tools.

## Installation

```bash
npm install @google/gemini-cli-sdk
```

## Usage

```typescript
import { GeminiCliAgent } from '@google/gemini-cli-sdk';

async function main() {
const agent = new GeminiCliAgent({
instructions: 'You are a helpful assistant.',
});

const controller = new AbortController();
const signal = controller.signal;

// Stream responses from the agent
const stream = agent.sendStream('Why is the sky blue?', signal);

for await (const chunk of stream) {
if (chunk.type === 'content') {
process.stdout.write(chunk.value.text || '');
}
}
}

main().catch(console.error);
```
38 changes: 38 additions & 0 deletions packages/sdk/examples/simple.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { GeminiCliAgent, tool, z } from '../src/index.js';

async function main() {
const myTool = tool(
{
name: 'add',
description: 'Add two numbers.',
inputSchema: z.object({
a: z.number().describe('the first number'),
b: z.number().describe('the second number'),
}),
},
async ({ a, b }) => {
console.log(`Tool 'add' called with a=${a}, b=${b}`);
return { result: a + b };
},
);

const agent = new GeminiCliAgent({
instructions: 'Make sure to always talk like a pirate.',
tools: [myTool],
});

console.log("Sending prompt: 'add 5 + 6'");
for await (const chunk of agent.sendStream(
'add 5 + 6 and tell me a story involving the result',
)) {
console.log(JSON.stringify(chunk, null, 2));
}
}

main().catch(console.error);
7 changes: 7 additions & 0 deletions packages/sdk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

export * from './src/index.js';
36 changes: 36 additions & 0 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "@google/gemini-cli-sdk",
"version": "0.29.0-nightly.20260203.71f46f116",
"description": "Gemini CLI SDK",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/google-gemini/gemini-cli.git"
},
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "node ../../scripts/build_package.js",
"lint": "eslint . --ext .ts,.tsx",
"format": "prettier --write .",
"test": "vitest run",
"test:ci": "vitest run",
"typecheck": "tsc --noEmit"
},
"files": [
"dist"
],
"dependencies": {
"@google/gemini-cli-core": "file:../core",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.23.1"
},
"devDependencies": {
"typescript": "^5.3.3",
"vitest": "^3.1.1"
},
"engines": {
"node": ">=20"
}
}
130 changes: 130 additions & 0 deletions packages/sdk/src/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {
Config,
type ConfigParameters,
PREVIEW_GEMINI_MODEL_AUTO,
GeminiEventType,
type ToolCallRequestInfo,
type ServerGeminiStreamEvent,
type GeminiClient,
scheduleAgentTools,
getAuthTypeFromEnv,
AuthType,
} from '@google/gemini-cli-core';

import { type Tool, SdkTool, type z } from './tool.js';

export interface GeminiCliAgentOptions {
instructions: string;
tools?: Array<Tool<z.ZodType>>;
model?: string;
cwd?: string;
debug?: boolean;
}

export class GeminiCliAgent {
private readonly config: Config;
private readonly tools: Array<Tool<z.ZodType>>;

constructor(options: GeminiCliAgentOptions) {
const cwd = options.cwd || process.cwd();
this.tools = options.tools || [];

const configParams: ConfigParameters = {
sessionId: `sdk-${Date.now()}`,
targetDir: cwd,
cwd,
debugMode: options.debug ?? false,
model: options.model || PREVIEW_GEMINI_MODEL_AUTO,
userMemory: options.instructions,
// Minimal config
enableHooks: false,
mcpEnabled: false,
extensionsEnabled: false,
};

this.config = new Config(configParams);
}

async *sendStream(
prompt: string,
signal?: AbortSignal,
): AsyncGenerator<ServerGeminiStreamEvent> {
// Lazy initialization of auth and client
if (!this.config.getContentGenerator()) {
const authType = getAuthTypeFromEnv() || AuthType.COMPUTE_ADC;

await this.config.refreshAuth(authType);
await this.config.initialize();

// Register tools now that registry exists
const registry = this.config.getToolRegistry();
const messageBus = this.config.getMessageBus();

for (const toolDef of this.tools) {
const sdkTool = new SdkTool(toolDef, messageBus);
registry.registerTool(sdkTool);
}
}

const client = this.config.getGeminiClient();

let request: Parameters<GeminiClient['sendMessageStream']>[0] = [
{ text: prompt },
];
const abortSignal = signal ?? new AbortController().signal;
const sessionId = this.config.getSessionId();

while (true) {
// sendMessageStream returns AsyncGenerator<ServerGeminiStreamEvent, Turn>
const stream = client.sendMessageStream(request, abortSignal, sessionId);

const toolCallsToSchedule: ToolCallRequestInfo[] = [];

for await (const event of stream) {
yield event;
if (event.type === GeminiEventType.ToolCallRequest) {
const toolCall = event.value;
let args = toolCall.args;
if (typeof args === 'string') {
args = JSON.parse(args);
}
toolCallsToSchedule.push({
...toolCall,
args,
isClientInitiated: false,
prompt_id: sessionId,
});
}
}

if (toolCallsToSchedule.length === 0) {
break;
}

const completedCalls = await scheduleAgentTools(
this.config,
toolCallsToSchedule,
{
schedulerId: sessionId,
toolRegistry: this.config.getToolRegistry(),
signal: abortSignal,
},
);

const functionResponses = completedCalls.flatMap(
(call) => call.response.responseParts,
);

// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
request = functionResponses as unknown as Parameters<
GeminiClient['sendMessageStream']
>[0];
}
}
}
8 changes: 8 additions & 0 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

export * from './agent.js';
export * from './tool.js';
Loading
Loading