feat(sdk): initial package bootstrap for SDK (#18861)

This commit is contained in:
Michael Bleigh
2026-02-12 22:08:27 -08:00
committed by GitHub
parent d82f66973f
commit bed3eae0e1
14 changed files with 451 additions and 15 deletions

View File

@@ -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
View File

@@ -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",

View File

@@ -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,

View File

@@ -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;

View File

@@ -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
View 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);
```

View 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
View 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
View 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
View 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];
}
}
}

View 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
View 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,
};
}

View 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" }]
}

View 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',
},
});