mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -07:00
feat(sdk): initial package bootstrap for SDK (#18861)
This commit is contained in:
@@ -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: {
|
||||
|
||||
21
package-lock.json
generated
21
package-lock.json
generated
@@ -1389,6 +1389,10 @@
|
||||
"resolved": "packages/core",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@google/gemini-cli-sdk": {
|
||||
"resolved": "packages/sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@google/gemini-cli-test-utils": {
|
||||
"resolved": "packages/test-utils",
|
||||
"link": true
|
||||
@@ -17557,6 +17561,23 @@
|
||||
"uuid": "dist-node/bin/uuid"
|
||||
}
|
||||
},
|
||||
"packages/sdk": {
|
||||
"name": "@google/gemini-cli-sdk",
|
||||
"version": "0.29.0-nightly.20260203.71f46f116",
|
||||
"license": "Apache-2.0",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"packages/test-utils": {
|
||||
"name": "@google/gemini-cli-test-utils",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
* 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';
|
||||
@@ -17,19 +17,6 @@ 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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
36
packages/sdk/README.md
Normal file
36
packages/sdk/README.md
Normal file
@@ -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
packages/sdk/examples/simple.ts
Normal file
38
packages/sdk/examples/simple.ts
Normal file
@@ -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
packages/sdk/index.ts
Normal file
7
packages/sdk/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
export * from './src/index.js';
|
||||
36
packages/sdk/package.json
Normal file
36
packages/sdk/package.json
Normal file
@@ -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
packages/sdk/src/agent.ts
Normal file
130
packages/sdk/src/agent.ts
Normal file
@@ -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
packages/sdk/src/index.ts
Normal file
8
packages/sdk/src/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
export * from './agent.js';
|
||||
export * from './tool.js';
|
||||
113
packages/sdk/src/tool.ts
Normal file
113
packages/sdk/src/tool.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { zodToJsonSchema } from 'zod-to-json-schema';
|
||||
import {
|
||||
BaseDeclarativeTool,
|
||||
BaseToolInvocation,
|
||||
type ToolResult,
|
||||
type ToolInvocation,
|
||||
Kind,
|
||||
type MessageBus,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
export { z };
|
||||
|
||||
export interface ToolDefinition<T extends z.ZodType> {
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: T;
|
||||
}
|
||||
|
||||
export interface Tool<T extends z.ZodType> extends ToolDefinition<T> {
|
||||
action: (params: z.infer<T>) => Promise<unknown>;
|
||||
}
|
||||
|
||||
class SdkToolInvocation<T extends z.ZodType> extends BaseToolInvocation<
|
||||
z.infer<T>,
|
||||
ToolResult
|
||||
> {
|
||||
constructor(
|
||||
params: z.infer<T>,
|
||||
messageBus: MessageBus,
|
||||
private readonly action: (params: z.infer<T>) => Promise<unknown>,
|
||||
toolName: string,
|
||||
) {
|
||||
super(params, messageBus, toolName);
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
return `Executing ${this._toolName}...`;
|
||||
}
|
||||
|
||||
async execute(
|
||||
_signal: AbortSignal,
|
||||
_updateOutput?: (output: string) => void,
|
||||
): Promise<ToolResult> {
|
||||
try {
|
||||
const result = await this.action(this.params);
|
||||
const output =
|
||||
typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
||||
return {
|
||||
llmContent: output,
|
||||
returnDisplay: output,
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
llmContent: `Error: ${errorMessage}`,
|
||||
returnDisplay: `Error: ${errorMessage}`,
|
||||
error: {
|
||||
message: errorMessage,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SdkTool<T extends z.ZodType> extends BaseDeclarativeTool<
|
||||
z.infer<T>,
|
||||
ToolResult
|
||||
> {
|
||||
constructor(
|
||||
private readonly definition: Tool<T>,
|
||||
messageBus: MessageBus,
|
||||
) {
|
||||
super(
|
||||
definition.name,
|
||||
definition.name,
|
||||
definition.description,
|
||||
Kind.Other,
|
||||
zodToJsonSchema(definition.inputSchema),
|
||||
messageBus,
|
||||
);
|
||||
}
|
||||
|
||||
protected createInvocation(
|
||||
params: z.infer<T>,
|
||||
messageBus: MessageBus,
|
||||
toolName?: string,
|
||||
): ToolInvocation<z.infer<T>, ToolResult> {
|
||||
return new SdkToolInvocation(
|
||||
params,
|
||||
messageBus,
|
||||
this.definition.action,
|
||||
toolName || this.name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function tool<T extends z.ZodType>(
|
||||
definition: ToolDefinition<T>,
|
||||
action: (params: z.infer<T>) => Promise<unknown>,
|
||||
): Tool<T> {
|
||||
return {
|
||||
...definition,
|
||||
action,
|
||||
};
|
||||
}
|
||||
12
packages/sdk/tsconfig.json
Normal file
12
packages/sdk/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"composite": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2023"],
|
||||
"types": ["node", "vitest/globals"]
|
||||
},
|
||||
"include": ["index.ts", "src/**/*.ts", "package.json"],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"references": [{ "path": "../core" }]
|
||||
}
|
||||
14
packages/sdk/vitest.config.ts
Normal file
14
packages/sdk/vitest.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user