osborn 0.1.6 → 0.5.3

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.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Smithery MCP Proxy — Bridges Smithery cloud MCP servers to Claude Agent SDK.
3
+ *
4
+ * The Claude Agent SDK's `type: 'http'` transport has a known bug (#18296, #7290)
5
+ * where it forces OAuth discovery on all HTTP MCP servers, ignoring configured
6
+ * auth headers. This proxy bypasses that by:
7
+ *
8
+ * 1. Using @smithery/api/mcp createConnection() to get a working transport
9
+ * 2. Connecting an MCP Client to the remote server via that transport
10
+ * 3. Listing available tools from the remote server
11
+ * 4. Creating a local McpServer with proxied tool handlers
12
+ * 5. Returning it as { type: 'sdk', name, instance } for the Claude Agent SDK
13
+ *
14
+ * This means the Claude SDK talks to our local McpServer (in-process, no HTTP),
15
+ * and our McpServer forwards tool calls to Smithery via the working transport.
16
+ */
17
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
18
+ import { SmitheryAuthorizationError } from '@smithery/api/mcp';
19
+ export interface SmitheryProxyConfig {
20
+ /** Display name for the MCP server (e.g., 'youtube') */
21
+ name: string;
22
+ /** Smithery Connect namespace (e.g., 'deer-y2fs') */
23
+ namespace: string;
24
+ /** Smithery Connect connection ID (e.g., 'youtube-mcp-sfiorini-TRmB') */
25
+ connectionId: string;
26
+ /** Upstream MCP server URL (e.g., 'https://youtube-mcp--sfiorini.run.tools') */
27
+ mcpUrl?: string;
28
+ }
29
+ /**
30
+ * Parse a Smithery Connect URL into namespace and connectionId.
31
+ * URL format: https://api.smithery.ai/connect/{namespace}/{connectionId}/mcp
32
+ */
33
+ export declare function parseSmitheryUrl(url: string): {
34
+ namespace: string;
35
+ connectionId: string;
36
+ } | null;
37
+ /**
38
+ * Create a proxy MCP server for a Smithery-hosted server.
39
+ *
40
+ * Returns a config object compatible with Claude Agent SDK's McpSdkServerConfigWithInstance.
41
+ * The proxy connects to Smithery via createConnection(), discovers tools, and registers
42
+ * them on a local McpServer that forwards calls to the remote server.
43
+ */
44
+ export declare function createSmitheryProxy(config: SmitheryProxyConfig): Promise<{
45
+ type: 'sdk';
46
+ name: string;
47
+ instance: McpServer;
48
+ }>;
49
+ /**
50
+ * Destroy an active proxy connection and clean up resources.
51
+ */
52
+ export declare function destroySmitheryProxy(name: string): Promise<void>;
53
+ /**
54
+ * Check if a URL is a Smithery Connect URL.
55
+ */
56
+ export declare function isSmitheryUrl(url: string): boolean;
57
+ export { SmitheryAuthorizationError };
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Smithery MCP Proxy — Bridges Smithery cloud MCP servers to Claude Agent SDK.
3
+ *
4
+ * The Claude Agent SDK's `type: 'http'` transport has a known bug (#18296, #7290)
5
+ * where it forces OAuth discovery on all HTTP MCP servers, ignoring configured
6
+ * auth headers. This proxy bypasses that by:
7
+ *
8
+ * 1. Using @smithery/api/mcp createConnection() to get a working transport
9
+ * 2. Connecting an MCP Client to the remote server via that transport
10
+ * 3. Listing available tools from the remote server
11
+ * 4. Creating a local McpServer with proxied tool handlers
12
+ * 5. Returning it as { type: 'sdk', name, instance } for the Claude Agent SDK
13
+ *
14
+ * This means the Claude SDK talks to our local McpServer (in-process, no HTTP),
15
+ * and our McpServer forwards tool calls to Smithery via the working transport.
16
+ */
17
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
18
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
19
+ import { createConnection, SmitheryAuthorizationError } from '@smithery/api/mcp';
20
+ import { z } from 'zod';
21
+ // Track active proxy connections for cleanup
22
+ const activeProxies = new Map();
23
+ /**
24
+ * Parse a Smithery Connect URL into namespace and connectionId.
25
+ * URL format: https://api.smithery.ai/connect/{namespace}/{connectionId}/mcp
26
+ */
27
+ export function parseSmitheryUrl(url) {
28
+ const match = url.match(/api\.smithery\.ai\/connect\/([^/]+)\/([^/]+)\/mcp/);
29
+ if (!match)
30
+ return null;
31
+ return { namespace: match[1], connectionId: match[2] };
32
+ }
33
+ /**
34
+ * Convert a JSON Schema property definition to a Zod type.
35
+ * Used to register proxy tools with proper parameter schemas so Claude
36
+ * knows what arguments each tool accepts.
37
+ */
38
+ function jsonSchemaPropertyToZod(prop, required) {
39
+ if (!prop)
40
+ return required ? z.any() : z.any().optional();
41
+ let zodType;
42
+ switch (prop.type) {
43
+ case 'string':
44
+ zodType = prop.enum ? z.enum(prop.enum) : z.string();
45
+ break;
46
+ case 'number':
47
+ case 'integer':
48
+ zodType = z.number();
49
+ break;
50
+ case 'boolean':
51
+ zodType = z.boolean();
52
+ break;
53
+ case 'array':
54
+ zodType = z.array(z.any());
55
+ break;
56
+ case 'object':
57
+ zodType = z.record(z.string(), z.any());
58
+ break;
59
+ default:
60
+ zodType = z.any();
61
+ }
62
+ if (prop.description) {
63
+ zodType = zodType.describe(prop.description);
64
+ }
65
+ if (!required) {
66
+ zodType = zodType.optional();
67
+ }
68
+ return zodType;
69
+ }
70
+ /**
71
+ * Convert a JSON Schema object to a Zod raw shape for McpServer.tool() registration.
72
+ */
73
+ function jsonSchemaToZodShape(inputSchema) {
74
+ const shape = {};
75
+ if (!inputSchema?.properties)
76
+ return shape;
77
+ const requiredFields = inputSchema.required || [];
78
+ for (const [propName, propDef] of Object.entries(inputSchema.properties)) {
79
+ const isRequired = requiredFields.includes(propName);
80
+ shape[propName] = jsonSchemaPropertyToZod(propDef, isRequired);
81
+ }
82
+ return shape;
83
+ }
84
+ /**
85
+ * Create a proxy MCP server for a Smithery-hosted server.
86
+ *
87
+ * Returns a config object compatible with Claude Agent SDK's McpSdkServerConfigWithInstance.
88
+ * The proxy connects to Smithery via createConnection(), discovers tools, and registers
89
+ * them on a local McpServer that forwards calls to the remote server.
90
+ */
91
+ export async function createSmitheryProxy(config) {
92
+ const { name, namespace, connectionId, mcpUrl } = config;
93
+ console.log(`🔌 Smithery proxy: connecting to ${name} (${namespace}/${connectionId})...`);
94
+ // Clean up any existing proxy for this name
95
+ await destroySmitheryProxy(name);
96
+ // 1. Get authenticated transport from Smithery SDK
97
+ const connection = await createConnection({
98
+ namespace,
99
+ connectionId,
100
+ mcpUrl,
101
+ });
102
+ console.log(`🔌 Smithery proxy: transport ready for ${name} (url: ${connection.url})`);
103
+ // 2. Connect MCP Client to the remote server
104
+ const remoteClient = new Client({ name: `${name}-proxy-client`, version: '1.0.0' }, { capabilities: {} });
105
+ await remoteClient.connect(connection.transport);
106
+ console.log(`🔌 Smithery proxy: client connected to ${name}`);
107
+ // 3. List tools from remote server
108
+ const toolsList = await remoteClient.listTools();
109
+ console.log(`🔌 Smithery proxy: ${name} has ${toolsList.tools.length} tools: ${toolsList.tools.map(t => t.name).join(', ')}`);
110
+ // 4. Create local McpServer and register proxied tools
111
+ const proxyServer = new McpServer({ name: `${name}-proxy`, version: '1.0.0' }, { capabilities: { tools: {} } });
112
+ for (const remoteTool of toolsList.tools) {
113
+ const zodShape = jsonSchemaToZodShape(remoteTool.inputSchema);
114
+ const hasParams = Object.keys(zodShape).length > 0;
115
+ if (hasParams) {
116
+ proxyServer.tool(remoteTool.name, remoteTool.description || '', zodShape, async (args) => {
117
+ console.log(`🔌 Smithery proxy [${name}]: calling ${remoteTool.name}`);
118
+ const result = await remoteClient.callTool({
119
+ name: remoteTool.name,
120
+ arguments: args,
121
+ });
122
+ return result;
123
+ });
124
+ }
125
+ else {
126
+ proxyServer.tool(remoteTool.name, remoteTool.description || '', async () => {
127
+ console.log(`🔌 Smithery proxy [${name}]: calling ${remoteTool.name}`);
128
+ const result = await remoteClient.callTool({
129
+ name: remoteTool.name,
130
+ arguments: {},
131
+ });
132
+ return result;
133
+ });
134
+ }
135
+ }
136
+ // Patch McpServer and its internal Server to allow reconnection across SDK query() calls.
137
+ // The Claude SDK connects to the McpServer on each query() but never disconnects,
138
+ // so the second query throws "Already connected to a transport". This patch
139
+ // auto-closes the previous transport before accepting a new one.
140
+ // We patch at both levels because McpServer.connect may throw before reaching Server.connect.
141
+ const proxyServerAny = proxyServer;
142
+ if (proxyServerAny.connect) {
143
+ const originalMcpConnect = proxyServerAny.connect.bind(proxyServerAny);
144
+ proxyServerAny.connect = async function (transport) {
145
+ if (this._transport) {
146
+ try {
147
+ await this._transport.close();
148
+ }
149
+ catch { }
150
+ this._transport = undefined;
151
+ }
152
+ const inner = this._server;
153
+ if (inner?._transport) {
154
+ try {
155
+ await inner._transport.close();
156
+ }
157
+ catch { }
158
+ inner._transport = undefined;
159
+ }
160
+ return originalMcpConnect(transport);
161
+ };
162
+ }
163
+ // Track for cleanup
164
+ activeProxies.set(name, { client: remoteClient, server: proxyServer });
165
+ console.log(`✅ Smithery proxy: ${name} ready with ${toolsList.tools.length} tools`);
166
+ return {
167
+ type: 'sdk',
168
+ name,
169
+ instance: proxyServer,
170
+ };
171
+ }
172
+ /**
173
+ * Destroy an active proxy connection and clean up resources.
174
+ */
175
+ export async function destroySmitheryProxy(name) {
176
+ const proxy = activeProxies.get(name);
177
+ if (proxy) {
178
+ try {
179
+ await proxy.client.close();
180
+ await proxy.server.close();
181
+ }
182
+ catch (e) {
183
+ // Ignore cleanup errors
184
+ }
185
+ activeProxies.delete(name);
186
+ console.log(`🔌 Smithery proxy: ${name} destroyed`);
187
+ }
188
+ }
189
+ /**
190
+ * Check if a URL is a Smithery Connect URL.
191
+ */
192
+ export function isSmitheryUrl(url) {
193
+ return url.includes('api.smithery.ai/connect/');
194
+ }
195
+ export { SmitheryAuthorizationError };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Status Manager - Handles background task status and conversational updates
3
+ *
4
+ * This module enables a Siri-like experience where:
5
+ * 1. Background research/tasks run independently
6
+ * 2. Status updates can be polled via a tool
7
+ * 3. The voice LLM can naturally ask about progress
8
+ * 4. Results are delivered conversationally
9
+ */
10
+ export interface TaskStatus {
11
+ id: string;
12
+ type: 'research' | 'execute' | 'search';
13
+ query: string;
14
+ status: 'pending' | 'running' | 'completed' | 'failed';
15
+ result?: string;
16
+ startedAt: number;
17
+ completedAt?: number;
18
+ progress?: number;
19
+ progressUpdates: string[];
20
+ lastUpdate?: string;
21
+ }
22
+ export interface StatusUpdate {
23
+ hasUpdates: boolean;
24
+ completedTasks: TaskStatus[];
25
+ runningTasks: TaskStatus[];
26
+ pendingTasks: TaskStatus[];
27
+ summary: string;
28
+ }
29
+ export declare class StatusManager {
30
+ private tasks;
31
+ private lastCheckTime;
32
+ private conversationContext;
33
+ /**
34
+ * Start tracking a new task (generates new ID)
35
+ */
36
+ startTask(type: TaskStatus['type'], query: string): string;
37
+ /**
38
+ * Register a task with a specific ID (used when brain already created the task)
39
+ */
40
+ registerTask(id: string, type: TaskStatus['type'], query: string): string;
41
+ /**
42
+ * Mark a task as running
43
+ */
44
+ markRunning(id: string, progress?: number): void;
45
+ /**
46
+ * Add a progress update to a running task (for interim voice updates)
47
+ */
48
+ addProgressUpdate(id: string, update: string): void;
49
+ /**
50
+ * Get the latest unspoken progress update for any running task
51
+ * Returns null if no new updates available
52
+ */
53
+ getLatestProgressUpdate(): {
54
+ taskId: string;
55
+ update: string;
56
+ } | null;
57
+ /**
58
+ * Check if there are new progress updates available
59
+ */
60
+ hasProgressUpdates(): boolean;
61
+ /**
62
+ * Complete a task with result
63
+ */
64
+ completeTask(id: string, result: string, success?: boolean): void;
65
+ /**
66
+ * Add context from conversation
67
+ */
68
+ addContext(context: string): void;
69
+ /**
70
+ * Get status update - called by the check_status tool
71
+ */
72
+ getStatusUpdate(): StatusUpdate;
73
+ /**
74
+ * Check if there are completed tasks to report
75
+ */
76
+ hasCompletedTasks(): boolean;
77
+ /**
78
+ * Get a brief status for voice announcement
79
+ */
80
+ getBriefStatus(): string;
81
+ /**
82
+ * Clear completed tasks after they've been reported
83
+ */
84
+ clearReportedTasks(): void;
85
+ /**
86
+ * Get context summary for the brain
87
+ */
88
+ getContextSummary(): string;
89
+ }
90
+ export declare const statusManager: StatusManager;
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Status Manager - Handles background task status and conversational updates
3
+ *
4
+ * This module enables a Siri-like experience where:
5
+ * 1. Background research/tasks run independently
6
+ * 2. Status updates can be polled via a tool
7
+ * 3. The voice LLM can naturally ask about progress
8
+ * 4. Results are delivered conversationally
9
+ */
10
+ export class StatusManager {
11
+ tasks = new Map();
12
+ lastCheckTime = Date.now();
13
+ conversationContext = [];
14
+ /**
15
+ * Start tracking a new task (generates new ID)
16
+ */
17
+ startTask(type, query) {
18
+ const id = `task-${Date.now()}-${Math.random().toString(36).substring(2, 6)}`;
19
+ return this.registerTask(id, type, query);
20
+ }
21
+ /**
22
+ * Register a task with a specific ID (used when brain already created the task)
23
+ */
24
+ registerTask(id, type, query) {
25
+ // Skip if task already exists
26
+ if (this.tasks.has(id)) {
27
+ console.log(`📋 [Status] Task already registered: ${id}`);
28
+ return id;
29
+ }
30
+ this.tasks.set(id, {
31
+ id,
32
+ type,
33
+ query,
34
+ status: 'pending',
35
+ startedAt: Date.now(),
36
+ progressUpdates: [],
37
+ });
38
+ console.log(`📋 [Status] Task registered: ${id} - ${query.substring(0, 50)}...`);
39
+ return id;
40
+ }
41
+ /**
42
+ * Mark a task as running
43
+ */
44
+ markRunning(id, progress) {
45
+ const task = this.tasks.get(id);
46
+ if (task) {
47
+ task.status = 'running';
48
+ if (progress !== undefined)
49
+ task.progress = progress;
50
+ }
51
+ }
52
+ /**
53
+ * Add a progress update to a running task (for interim voice updates)
54
+ */
55
+ addProgressUpdate(id, update) {
56
+ const task = this.tasks.get(id);
57
+ if (task) {
58
+ task.progressUpdates.push(update);
59
+ task.lastUpdate = update;
60
+ console.log(`📊 [Status] Progress update for ${id.substring(0, 12)}: ${update.substring(0, 60)}...`);
61
+ }
62
+ }
63
+ /**
64
+ * Get the latest unspoken progress update for any running task
65
+ * Returns null if no new updates available
66
+ */
67
+ getLatestProgressUpdate() {
68
+ const runningTasks = Array.from(this.tasks.values()).filter(t => t.status === 'running');
69
+ for (const task of runningTasks) {
70
+ if (task.lastUpdate) {
71
+ const update = task.lastUpdate;
72
+ task.lastUpdate = undefined; // Mark as consumed
73
+ return { taskId: task.id, update };
74
+ }
75
+ }
76
+ return null;
77
+ }
78
+ /**
79
+ * Check if there are new progress updates available
80
+ */
81
+ hasProgressUpdates() {
82
+ return Array.from(this.tasks.values()).some(t => t.status === 'running' && t.lastUpdate);
83
+ }
84
+ /**
85
+ * Complete a task with result
86
+ */
87
+ completeTask(id, result, success = true) {
88
+ const task = this.tasks.get(id);
89
+ if (task) {
90
+ task.status = success ? 'completed' : 'failed';
91
+ task.result = result;
92
+ task.completedAt = Date.now();
93
+ console.log(`✅ [Status] Task ${success ? 'completed' : 'failed'}: ${id}`);
94
+ }
95
+ }
96
+ /**
97
+ * Add context from conversation
98
+ */
99
+ addContext(context) {
100
+ this.conversationContext.push(context);
101
+ if (this.conversationContext.length > 10) {
102
+ this.conversationContext.shift();
103
+ }
104
+ }
105
+ /**
106
+ * Get status update - called by the check_status tool
107
+ */
108
+ getStatusUpdate() {
109
+ const now = Date.now();
110
+ const timeSinceLastCheck = now - this.lastCheckTime;
111
+ this.lastCheckTime = now;
112
+ const allTasks = Array.from(this.tasks.values());
113
+ const completedTasks = allTasks.filter(t => t.status === 'completed' && t.completedAt && t.completedAt > now - 60000 // Last minute
114
+ );
115
+ const runningTasks = allTasks.filter(t => t.status === 'running');
116
+ const pendingTasks = allTasks.filter(t => t.status === 'pending');
117
+ // Generate natural summary
118
+ let summary = '';
119
+ if (completedTasks.length > 0) {
120
+ const results = completedTasks.map(t => {
121
+ const shortResult = t.result?.substring(0, 200) || 'No result';
122
+ return `${t.query}: ${shortResult}`;
123
+ }).join('\n');
124
+ summary = `I found some results:\n${results}`;
125
+ }
126
+ else if (runningTasks.length > 0) {
127
+ const tasks = runningTasks.map(t => t.query).join(', ');
128
+ const elapsed = Math.round((now - runningTasks[0].startedAt) / 1000);
129
+ summary = `Still working on: ${tasks} (${elapsed}s elapsed)`;
130
+ }
131
+ else if (pendingTasks.length > 0) {
132
+ summary = `${pendingTasks.length} tasks queued, starting soon...`;
133
+ }
134
+ else {
135
+ summary = "No active tasks. What would you like me to work on?";
136
+ }
137
+ return {
138
+ hasUpdates: completedTasks.length > 0,
139
+ completedTasks,
140
+ runningTasks,
141
+ pendingTasks,
142
+ summary,
143
+ };
144
+ }
145
+ /**
146
+ * Check if there are completed tasks to report
147
+ */
148
+ hasCompletedTasks() {
149
+ return Array.from(this.tasks.values()).some(t => t.status === 'completed' &&
150
+ t.completedAt &&
151
+ t.completedAt > this.lastCheckTime);
152
+ }
153
+ /**
154
+ * Get a brief status for voice announcement
155
+ */
156
+ getBriefStatus() {
157
+ const running = Array.from(this.tasks.values()).filter(t => t.status === 'running');
158
+ const completed = Array.from(this.tasks.values()).filter(t => t.status === 'completed');
159
+ if (running.length > 0) {
160
+ return `Working on ${running.length} task${running.length > 1 ? 's' : ''}...`;
161
+ }
162
+ if (completed.length > 0) {
163
+ return `I have ${completed.length} result${completed.length > 1 ? 's' : ''} ready.`;
164
+ }
165
+ return "Ready for your next request.";
166
+ }
167
+ /**
168
+ * Clear completed tasks after they've been reported
169
+ */
170
+ clearReportedTasks() {
171
+ const now = Date.now();
172
+ for (const [id, task] of this.tasks.entries()) {
173
+ // Remove tasks that completed more than 2 minutes ago
174
+ if (task.status === 'completed' && task.completedAt && task.completedAt < now - 120000) {
175
+ this.tasks.delete(id);
176
+ }
177
+ }
178
+ }
179
+ /**
180
+ * Get context summary for the brain
181
+ */
182
+ getContextSummary() {
183
+ return this.conversationContext.slice(-5).join(' | ');
184
+ }
185
+ }
186
+ // Singleton instance
187
+ export const statusManager = new StatusManager();
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Voice I/O Module
3
+ * Handles STT (Speech-to-Text), TTS (Text-to-Speech), and Realtime model creation
4
+ *
5
+ * Supports two modes:
6
+ * - Direct mode: STT (Deepgram) → Claude Agent SDK → TTS (Deepgram)
7
+ * - Realtime mode: OpenAI/Gemini native speech-to-speech models
8
+ */
9
+ import * as deepgram from '@livekit/agents-plugin-deepgram';
10
+ import * as google from '@livekit/agents-plugin-google';
11
+ import * as openai from '@livekit/agents-plugin-openai';
12
+ import * as silero from '@livekit/agents-plugin-silero';
13
+ import type { RealtimeConfig } from './config.js';
14
+ export interface STTConfig {
15
+ provider: 'deepgram' | 'groq-whisper' | 'openai-whisper';
16
+ model?: string;
17
+ language?: string;
18
+ }
19
+ export interface TTSConfig {
20
+ provider: 'gemini' | 'openai' | 'elevenlabs' | 'deepgram';
21
+ voice?: string;
22
+ model?: string;
23
+ }
24
+ export interface VoiceIOConfig {
25
+ stt: STTConfig;
26
+ tts: TTSConfig;
27
+ }
28
+ /**
29
+ * Create STT (Speech-to-Text) instance based on config
30
+ * Note: Gemini STT is not available in Node.js, using Deepgram as default
31
+ */
32
+ export declare function createSTT(config: STTConfig): deepgram.STT | openai.STT;
33
+ /**
34
+ * Create TTS (Text-to-Speech) instance based on config
35
+ * Using Gemini TTS as default (cheaper, good quality)
36
+ */
37
+ export declare function createTTS(config: TTSConfig): any;
38
+ /**
39
+ * Create VAD (Voice Activity Detection) for turn detection
40
+ *
41
+ * Tuned to prevent:
42
+ * - "Audio file is too short" errors from STT (OpenAI requires >= 0.1s)
43
+ * - Split sentences when user pauses briefly mid-speech
44
+ * - False triggers from ambient noise
45
+ */
46
+ export declare function createVAD(): Promise<silero.VAD>;
47
+ /**
48
+ * Default voice I/O configuration
49
+ * Uses Deepgram STT (fast, accurate) + Deepgram TTS (fast, good)
50
+ */
51
+ export declare const DEFAULT_VOICE_IO_CONFIG: VoiceIOConfig;
52
+ export interface RealtimeModelConfig {
53
+ provider: 'openai' | 'gemini';
54
+ openaiVoice?: 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer';
55
+ openaiModel?: string;
56
+ geminiVoice?: 'Puck' | 'Charon' | 'Kore' | 'Fenrir' | 'Aoede';
57
+ geminiModel?: string;
58
+ instructions?: string;
59
+ }
60
+ /**
61
+ * Create Realtime Model for native speech-to-speech
62
+ * Supports OpenAI Realtime API and Gemini Live API
63
+ *
64
+ * Note: Instructions are passed to voice.Agent, not to the RealtimeModel
65
+ */
66
+ export declare function createRealtimeModel(config: RealtimeModelConfig): google.beta.realtime.RealtimeModel | openai.realtime.RealtimeModel;
67
+ /**
68
+ * Create realtime model from config
69
+ */
70
+ export declare function createRealtimeModelFromConfig(realtimeConfig: RealtimeConfig, instructions?: string): google.beta.realtime.RealtimeModel | openai.realtime.RealtimeModel;