@k2wanko/gemini-cli-sdk 0.2.1 → 0.4.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 CHANGED
@@ -5,8 +5,11 @@ Built on top of `@google/gemini-cli-core`.
5
5
 
6
6
  ## Features
7
7
 
8
+ - **Sub-agents** — Delegate tasks to specialized child agents via `defineSubAgent()` (programmatic) or `loadSubAgents()` (markdown files), supporting both local Gemini agents and remote A2A protocol agents
8
9
  - **Skill support** — Load skill directories compatible with Gemini CLI's skill format
9
10
  - **Non-interactive by default** — All tool calls are auto-approved, designed for headless agent usage
11
+ - **Logging control** — Suppress noisy core logs by default (`"silent"`), or route them to a custom logger (pino, winston, etc.) via `logLevel` and `logger` options
12
+ - **Hooks** — Run shell commands at lifecycle events (BeforeTool, AfterTool, BeforeAgent, etc.) to inject context, block operations, or audit tool calls — compatible with Gemini CLI's hook protocol
10
13
 
11
14
  ## Install
12
15
 
package/dist/agent.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- import { GeminiEventType, type ServerGeminiStreamEvent } from "@google/gemini-cli-core";
1
+ import { Config, GeminiEventType, type HookDefinition, HookEventName, HookType, type ServerGeminiStreamEvent } from "@google/gemini-cli-core";
2
2
  import { type SessionContext } from "./context.js";
3
+ import { type Logger, type LogLevel } from "./logger.js";
3
4
  import type { SkillRef } from "./skills.js";
4
5
  import { type ToolDef } from "./tool.js";
5
- export { GeminiEventType, type ServerGeminiStreamEvent };
6
+ export { GeminiEventType, HookEventName, HookType, type HookDefinition, type ServerGeminiStreamEvent, };
6
7
  export interface GeminiAgentOptions {
7
8
  instructions: string | ((ctx: SessionContext) => string | Promise<string>);
8
9
  tools?: ToolDef<any>[];
@@ -14,6 +15,12 @@ export interface GeminiAgentOptions {
14
15
  sessionId?: string;
15
16
  /** Context compression threshold (0-1 fraction) */
16
17
  compressionThreshold?: number;
18
+ /** Core logger verbosity — default: "silent" */
19
+ logLevel?: LogLevel;
20
+ /** Custom log destination — defaults to console when logLevel is not "silent" */
21
+ logger?: Logger;
22
+ /** Hook definitions — automatically enables the hook system when provided */
23
+ hooks?: Partial<Record<HookEventName, HookDefinition[]>>;
17
24
  }
18
25
  export declare class GeminiAgent {
19
26
  private readonly config;
@@ -25,6 +32,12 @@ export declare class GeminiAgent {
25
32
  constructor(options: GeminiAgentOptions);
26
33
  /** Return the session ID assigned to this agent instance */
27
34
  getSessionId(): string;
35
+ /**
36
+ * Return the underlying core Config instance.
37
+ * Useful for advanced use cases such as sub-agent execution.
38
+ * The Config is lazily initialized — call after the first sendStream().
39
+ */
40
+ getCoreConfig(): Config;
28
41
  /** List available sessions for the current project */
29
42
  listSessions(): Promise<import("./session.js").SessionInfo[]>;
30
43
  sendStream(prompt: string, signal?: AbortSignal): AsyncGenerator<ServerGeminiStreamEvent>;
package/dist/agent.js CHANGED
@@ -1,8 +1,9 @@
1
- import { ActivateSkillTool, AuthType, Config, GeminiEventType, getAuthTypeFromEnv, loadSkillsFromDir, PolicyDecision, PREVIEW_GEMINI_MODEL_AUTO, scheduleAgentTools, } from "@google/gemini-cli-core";
1
+ import { ActivateSkillTool, AuthType, Config, GeminiEventType, getAuthTypeFromEnv, HookEventName, HookType, loadSkillsFromDir, PolicyDecision, PREVIEW_GEMINI_MODEL_AUTO, scheduleAgentTools, } from "@google/gemini-cli-core";
2
2
  import { AgentFsImpl, AgentShellImpl } from "./context.js";
3
+ import { patchCoreLogger } from "./logger.js";
3
4
  import { listSessions, loadSession, messageRecordsToHistory, } from "./session.js";
4
5
  import { SdkTool } from "./tool.js";
5
- export { GeminiEventType };
6
+ export { GeminiEventType, HookEventName, HookType, };
6
7
  export class GeminiAgent {
7
8
  config;
8
9
  tools;
@@ -11,6 +12,7 @@ export class GeminiAgent {
11
12
  resumeSessionId;
12
13
  instructionsLoaded = false;
13
14
  constructor(options) {
15
+ patchCoreLogger(options.logLevel ?? "silent", options.logger);
14
16
  this.instructions = options.instructions;
15
17
  this.tools = options.tools ?? [];
16
18
  this.skillRefs = options.skills ?? [];
@@ -24,7 +26,8 @@ export class GeminiAgent {
24
26
  debugMode: options.debug ?? false,
25
27
  model: options.model ?? PREVIEW_GEMINI_MODEL_AUTO,
26
28
  userMemory: initialMemory,
27
- enableHooks: false,
29
+ enableHooks: !!options.hooks,
30
+ ...(options.hooks && { hooks: options.hooks }),
28
31
  mcpEnabled: false,
29
32
  extensionsEnabled: false,
30
33
  skillsSupport: true,
@@ -40,6 +43,14 @@ export class GeminiAgent {
40
43
  getSessionId() {
41
44
  return this.config.getSessionId();
42
45
  }
46
+ /**
47
+ * Return the underlying core Config instance.
48
+ * Useful for advanced use cases such as sub-agent execution.
49
+ * The Config is lazily initialized — call after the first sendStream().
50
+ */
51
+ getCoreConfig() {
52
+ return this.config;
53
+ }
43
54
  /** List available sessions for the current project */
44
55
  async listSessions() {
45
56
  await this.ensureInitialized();
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from "./agent.js";
2
2
  export * from "./context.js";
3
+ export * from "./logger.js";
3
4
  export * from "./session.js";
4
5
  export * from "./skills.js";
6
+ export * from "./subagent.js";
5
7
  export * from "./tool.js";
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from "./agent.js";
2
2
  export * from "./context.js";
3
+ export * from "./logger.js";
3
4
  export * from "./session.js";
4
5
  export * from "./skills.js";
6
+ export * from "./subagent.js";
5
7
  export * from "./tool.js";
@@ -0,0 +1,8 @@
1
+ export type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
2
+ export interface Logger {
3
+ log?: (...args: unknown[]) => void;
4
+ warn?: (...args: unknown[]) => void;
5
+ error?: (...args: unknown[]) => void;
6
+ debug?: (...args: unknown[]) => void;
7
+ }
8
+ export declare function patchCoreLogger(level: LogLevel, logger?: Logger): void;
package/dist/logger.js ADDED
@@ -0,0 +1,21 @@
1
+ import { debugLogger } from "@google/gemini-cli-core";
2
+ const LOG_LEVEL_ORDER = {
3
+ silent: 0,
4
+ error: 1,
5
+ warn: 2,
6
+ info: 3,
7
+ debug: 4,
8
+ };
9
+ const noop = () => { };
10
+ export function patchCoreLogger(level, logger) {
11
+ const threshold = LOG_LEVEL_ORDER[level];
12
+ const dest = logger ?? console;
13
+ debugLogger.log =
14
+ threshold >= LOG_LEVEL_ORDER.info ? (dest.log ?? noop).bind(dest) : noop;
15
+ debugLogger.warn =
16
+ threshold >= LOG_LEVEL_ORDER.warn ? (dest.warn ?? noop).bind(dest) : noop;
17
+ debugLogger.error =
18
+ threshold >= LOG_LEVEL_ORDER.error ? (dest.error ?? noop).bind(dest) : noop;
19
+ debugLogger.debug =
20
+ threshold >= LOG_LEVEL_ORDER.debug ? (dest.debug ?? noop).bind(dest) : noop;
21
+ }
@@ -0,0 +1,58 @@
1
+ import { AgentTerminateMode, type LocalAgentDefinition, type RemoteAgentDefinition, type SubagentActivityEvent } from "@google/gemini-cli-core";
2
+ import { z } from "zod";
3
+ import type { SessionContext } from "./context.js";
4
+ import { type ToolDef } from "./tool.js";
5
+ export { AgentTerminateMode, type SubagentActivityEvent, type LocalAgentDefinition, type RemoteAgentDefinition, };
6
+ /**
7
+ * A prompt value that can be a static string or a function that dynamically
8
+ * resolves at invocation time. The function receives the tool input params
9
+ * and the parent's SessionContext.
10
+ *
11
+ * The resolved string still supports core's `${paramName}` template syntax,
12
+ * so both mechanisms can be combined.
13
+ */
14
+ export type PromptValue<TInput extends z.ZodRawShape> = string | ((params: z.infer<z.ZodObject<TInput>>, ctx: SessionContext) => string | Promise<string>);
15
+ export interface SubAgentOptions<TInput extends z.ZodRawShape> {
16
+ name: string;
17
+ description: string;
18
+ inputSchema: z.ZodObject<TInput>;
19
+ /**
20
+ * System prompt — a static string or a function for dynamic resolution.
21
+ * Supports core's `${paramName}` template syntax (applied after resolution).
22
+ */
23
+ systemPrompt: PromptValue<TInput>;
24
+ /**
25
+ * Initial query — a static string or a function for dynamic resolution.
26
+ * Supports core's `${paramName}` template syntax (applied after resolution).
27
+ * Default: core's "Get Started!"
28
+ */
29
+ query?: PromptValue<TInput>;
30
+ /** Model. Default: "inherit" (parent's active model) */
31
+ model?: string;
32
+ /** Max turns. Omit to use core default. */
33
+ maxTurns?: number;
34
+ /** Max execution time in minutes. Omit to use core default. */
35
+ maxTimeMinutes?: number;
36
+ /**
37
+ * Tools the sub-agent can use (tool names from parent registry).
38
+ * Include "activate_skill" to enable skills.
39
+ * If omitted: inherits ALL parent tools.
40
+ */
41
+ tools?: string[];
42
+ /** Activity callback for observability */
43
+ onActivity?: (event: SubagentActivityEvent) => void;
44
+ }
45
+ /**
46
+ * Define a local sub-agent programmatically. Returns a `ToolDef` compatible
47
+ * with `GeminiAgent.tools`.
48
+ */
49
+ export declare function defineSubAgent<TInput extends z.ZodRawShape>(options: SubAgentOptions<TInput>): ToolDef<z.ZodObject<TInput>>;
50
+ /**
51
+ * Load sub-agent definitions from `.md` files in a directory.
52
+ * Each file becomes a `ToolDef` compatible with `GeminiAgent.tools`.
53
+ *
54
+ * Files starting with `_` are ignored by core's loader.
55
+ */
56
+ export declare function loadSubAgents(dir: string, options?: {
57
+ onActivity?: (event: SubagentActivityEvent) => void;
58
+ }): Promise<ToolDef<any>[]>;
@@ -0,0 +1,223 @@
1
+ import { ClientFactory } from "@a2a-js/sdk/client";
2
+ import { AgentTerminateMode, LocalAgentExecutor, loadAgentsFromDirectory, } from "@google/gemini-cli-core";
3
+ import { z } from "zod";
4
+ import { ToolError } from "./tool.js";
5
+ // ---------------------------------------------------------------------------
6
+ // Re-exports from core
7
+ // ---------------------------------------------------------------------------
8
+ export { AgentTerminateMode, };
9
+ // ---------------------------------------------------------------------------
10
+ // defineSubAgent — Programmatic API
11
+ // ---------------------------------------------------------------------------
12
+ /**
13
+ * Define a local sub-agent programmatically. Returns a `ToolDef` compatible
14
+ * with `GeminiAgent.tools`.
15
+ */
16
+ export function defineSubAgent(options) {
17
+ const hasDynamicPrompt = typeof options.systemPrompt === "function" ||
18
+ typeof options.query === "function";
19
+ // Fast path: when both prompts are static strings, build the definition
20
+ // upfront and delegate to the shared helper.
21
+ if (!hasDynamicPrompt) {
22
+ const def = buildLocalAgentDef(options, options.systemPrompt, options.query);
23
+ return wrapLocalAgentAsTool(def, options.inputSchema, options.onActivity);
24
+ }
25
+ // Dynamic path: resolve prompt values inside the action.
26
+ return {
27
+ name: options.name,
28
+ description: options.description,
29
+ inputSchema: options.inputSchema,
30
+ sendErrorsToModel: true,
31
+ action: async (params, ctx) => {
32
+ const resolvedSystemPrompt = await resolvePromptValue(options.systemPrompt, params, ctx);
33
+ const resolvedQuery = options.query !== undefined
34
+ ? await resolvePromptValue(options.query, params, ctx)
35
+ : undefined;
36
+ const def = buildLocalAgentDef(options, resolvedSystemPrompt, resolvedQuery);
37
+ return executeLocalAgent(def, params, ctx, options.onActivity);
38
+ },
39
+ };
40
+ }
41
+ // ---------------------------------------------------------------------------
42
+ // loadSubAgents — File-based API
43
+ // ---------------------------------------------------------------------------
44
+ /**
45
+ * Load sub-agent definitions from `.md` files in a directory.
46
+ * Each file becomes a `ToolDef` compatible with `GeminiAgent.tools`.
47
+ *
48
+ * Files starting with `_` are ignored by core's loader.
49
+ */
50
+ export async function loadSubAgents(dir, options) {
51
+ const { agents, errors } = await loadAgentsFromDirectory(dir);
52
+ if (errors.length > 0) {
53
+ for (const err of errors) {
54
+ console.error(`[subagent] Failed to load ${err.filePath}: ${err.message}`);
55
+ }
56
+ }
57
+ const querySchema = z.object({
58
+ query: z.string().optional().describe("Input query for the sub-agent"),
59
+ });
60
+ const tools = [];
61
+ for (const def of agents) {
62
+ if (def.kind === "local") {
63
+ tools.push(wrapLocalAgentAsTool(def, querySchema, options?.onActivity));
64
+ }
65
+ else if (def.kind === "remote") {
66
+ tools.push(wrapRemoteAgentAsTool(def, querySchema));
67
+ }
68
+ }
69
+ return tools;
70
+ }
71
+ // ---------------------------------------------------------------------------
72
+ // Internal: prompt resolution
73
+ // ---------------------------------------------------------------------------
74
+ async function resolvePromptValue(value, params, ctx) {
75
+ return typeof value === "function" ? value(params, ctx) : value;
76
+ }
77
+ // ---------------------------------------------------------------------------
78
+ // Internal: build LocalAgentDefinition from SubAgentOptions + resolved strings
79
+ // ---------------------------------------------------------------------------
80
+ function buildLocalAgentDef(options, systemPrompt, query) {
81
+ return {
82
+ kind: "local",
83
+ name: options.name,
84
+ description: options.description,
85
+ inputConfig: {
86
+ inputSchema: zodToJsonSchemaSimple(options.inputSchema),
87
+ },
88
+ promptConfig: {
89
+ systemPrompt,
90
+ ...(query !== undefined && { query }),
91
+ },
92
+ modelConfig: { model: options.model ?? "inherit" },
93
+ runConfig: {
94
+ ...(options.maxTurns !== undefined && { maxTurns: options.maxTurns }),
95
+ ...(options.maxTimeMinutes !== undefined && {
96
+ maxTimeMinutes: options.maxTimeMinutes,
97
+ }),
98
+ },
99
+ ...(options.tools && { toolConfig: { tools: options.tools } }),
100
+ };
101
+ }
102
+ // ---------------------------------------------------------------------------
103
+ // Internal: execute a local agent (shared by wrapLocalAgentAsTool & dynamic path)
104
+ // ---------------------------------------------------------------------------
105
+ async function executeLocalAgent(def, params, ctx, onActivity) {
106
+ const config = ctx.agent.getCoreConfig();
107
+ // Register model config alias — replicates AgentRegistry.registerModelConfigs()
108
+ const alias = `${def.name}-config`;
109
+ config.modelConfigService.registerRuntimeModelConfig(alias, {
110
+ modelConfig: {
111
+ model: def.modelConfig.model === "inherit"
112
+ ? config.getActiveModel()
113
+ : def.modelConfig.model,
114
+ ...(def.modelConfig.generateContentConfig && {
115
+ generateContentConfig: def.modelConfig.generateContentConfig,
116
+ }),
117
+ },
118
+ });
119
+ const executor = await LocalAgentExecutor.create(def, config, onActivity);
120
+ const result = await executor.run(params, new AbortController().signal);
121
+ if (result.terminate_reason !== AgentTerminateMode.GOAL) {
122
+ throw new ToolError(`Sub-agent "${def.name}" terminated: ${result.terminate_reason}`);
123
+ }
124
+ return result.result;
125
+ }
126
+ // ---------------------------------------------------------------------------
127
+ // Internal: wrapLocalAgentAsTool
128
+ // ---------------------------------------------------------------------------
129
+ function wrapLocalAgentAsTool(def, inputSchema, onActivity) {
130
+ return {
131
+ name: def.name,
132
+ description: def.description,
133
+ inputSchema,
134
+ sendErrorsToModel: true,
135
+ action: async (params, ctx) => {
136
+ return executeLocalAgent(def, params, ctx, onActivity);
137
+ },
138
+ };
139
+ }
140
+ // ---------------------------------------------------------------------------
141
+ // Internal: wrapRemoteAgentAsTool
142
+ // ---------------------------------------------------------------------------
143
+ /** Cached ClientFactory instance (shared across all remote agents) */
144
+ let sharedClientFactory;
145
+ function getClientFactory() {
146
+ if (!sharedClientFactory) {
147
+ sharedClientFactory = new ClientFactory();
148
+ }
149
+ return sharedClientFactory;
150
+ }
151
+ function wrapRemoteAgentAsTool(def, inputSchema) {
152
+ return {
153
+ name: def.name,
154
+ description: def.description,
155
+ inputSchema,
156
+ sendErrorsToModel: true,
157
+ action: async (params) => {
158
+ const factory = getClientFactory();
159
+ const client = await factory.createFromUrl(def.agentCardUrl, "");
160
+ const query = typeof params === "object" && params !== null && "query" in params
161
+ ? String(params.query ?? "")
162
+ : JSON.stringify(params);
163
+ const result = await client.sendMessage({
164
+ message: {
165
+ kind: "message",
166
+ messageId: crypto.randomUUID(),
167
+ role: "user",
168
+ parts: [{ kind: "text", text: query }],
169
+ },
170
+ configuration: { blocking: true },
171
+ });
172
+ return extractTextFromResult(result);
173
+ },
174
+ };
175
+ }
176
+ // ---------------------------------------------------------------------------
177
+ // Internal: A2A text extraction
178
+ // ---------------------------------------------------------------------------
179
+ function extractTextFromResult(result) {
180
+ if (result.kind === "message") {
181
+ return extractTextFromParts(result.parts);
182
+ }
183
+ // Task
184
+ const parts = result.status?.message?.parts;
185
+ if (parts) {
186
+ return extractTextFromParts(parts);
187
+ }
188
+ return "";
189
+ }
190
+ function extractTextFromParts(parts) {
191
+ return parts
192
+ .filter((p) => p.kind === "text")
193
+ .map((p) => p.text)
194
+ .join("\n");
195
+ }
196
+ // ---------------------------------------------------------------------------
197
+ // Internal: minimal Zod-to-JSON-Schema for inputConfig
198
+ // ---------------------------------------------------------------------------
199
+ function zodToJsonSchemaSimple(schema) {
200
+ const shape = schema.shape;
201
+ const properties = {};
202
+ const required = [];
203
+ for (const [key, value] of Object.entries(shape)) {
204
+ const zodType = value;
205
+ const prop = { type: "string" };
206
+ if (zodType.description) {
207
+ prop.description = zodType.description;
208
+ }
209
+ // Unwrap optional
210
+ if (zodType instanceof z.ZodOptional) {
211
+ // optional — don't add to required
212
+ }
213
+ else {
214
+ required.push(key);
215
+ }
216
+ properties[key] = prop;
217
+ }
218
+ return {
219
+ type: "object",
220
+ properties,
221
+ ...(required.length > 0 && { required }),
222
+ };
223
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k2wanko/gemini-cli-sdk",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "A lightweight SDK for building non-interactive AI agents powered by Google Gemini",
5
5
  "type": "module",
6
6
  "private": false,
@@ -43,6 +43,7 @@
43
43
  "test": "bun test"
44
44
  },
45
45
  "dependencies": {
46
+ "@a2a-js/sdk": "^0.3.10",
46
47
  "@google/gemini-cli-core": "0.30.0-nightly.20260218.ce84b3cb5",
47
48
  "zod": "^3.23.8",
48
49
  "zod-to-json-schema": "^3.23.1"