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,144 @@
1
+ /**
2
+ * Codex LLM Wrapper for LiveKit Agents
3
+ *
4
+ * Wraps the Codex Agent SDK (@openai/codex-sdk) to work
5
+ * with LiveKit's AgentSession as an LLM provider.
6
+ *
7
+ * Flow: User speaks → STT → CodexLLM (Agent SDK) → TTS → User hears
8
+ */
9
+ import { llm, shortuuid, DEFAULT_API_CONNECT_OPTIONS } from '@livekit/agents';
10
+ import { Codex } from '@openai/codex-sdk';
11
+ import { EventEmitter } from 'events';
12
+ /**
13
+ * Codex LLM - Wraps Codex Agent SDK for LiveKit
14
+ */
15
+ export class CodexLLM extends llm.LLM {
16
+ #opts;
17
+ #codex;
18
+ #thread = null;
19
+ #eventEmitter;
20
+ constructor(opts = {}) {
21
+ super();
22
+ this.#opts = {
23
+ workingDirectory: opts.workingDirectory || process.cwd(),
24
+ skipGitRepoCheck: opts.skipGitRepoCheck ?? true,
25
+ };
26
+ this.#codex = new Codex();
27
+ this.#eventEmitter = opts.eventEmitter || new EventEmitter();
28
+ console.log('🟣 CodexLLM initialized (Codex Agent SDK)');
29
+ console.log(` 📁 Working dir: ${this.#opts.workingDirectory}`);
30
+ }
31
+ label() {
32
+ return 'codex.agent-sdk';
33
+ }
34
+ get model() {
35
+ return 'codex';
36
+ }
37
+ get events() {
38
+ return this.#eventEmitter;
39
+ }
40
+ get thread() {
41
+ return this.#thread;
42
+ }
43
+ chat({ chatCtx, toolCtx, connOptions = DEFAULT_API_CONNECT_OPTIONS, }) {
44
+ return new CodexLLMStream(this, {
45
+ chatCtx,
46
+ toolCtx,
47
+ connOptions,
48
+ opts: this.#opts,
49
+ codex: this.#codex,
50
+ thread: this.#thread,
51
+ onThread: (t) => { this.#thread = t; },
52
+ eventEmitter: this.#eventEmitter,
53
+ });
54
+ }
55
+ }
56
+ /**
57
+ * Codex LLM Stream - Runs Codex Agent SDK and streams results
58
+ */
59
+ class CodexLLMStream extends llm.LLMStream {
60
+ #opts;
61
+ #codex;
62
+ #thread;
63
+ #onThread;
64
+ #eventEmitter;
65
+ constructor(llmInstance, { chatCtx, toolCtx, connOptions, opts, codex, thread, onThread, eventEmitter, }) {
66
+ super(llmInstance, { chatCtx, toolCtx, connOptions });
67
+ this.#opts = opts;
68
+ this.#codex = codex;
69
+ this.#thread = thread;
70
+ this.#onThread = onThread;
71
+ this.#eventEmitter = eventEmitter;
72
+ }
73
+ async run() {
74
+ const requestId = `codex_${shortuuid()}`;
75
+ try {
76
+ // Extract user's message from chat context
77
+ // ChatContext has .items which are ChatItem[] (ChatMessage | FunctionCall | FunctionCallOutput)
78
+ const items = this.chatCtx.items;
79
+ // Find the last user message
80
+ let userText = '';
81
+ for (let i = items.length - 1; i >= 0; i--) {
82
+ const item = items[i];
83
+ if (item.type === 'message' && item.role === 'user') {
84
+ // Content is ChatContent[] = (ImageContent | AudioContent | string)[]
85
+ if (Array.isArray(item.content)) {
86
+ userText = item.content
87
+ .filter((c) => typeof c === 'string')
88
+ .join('\n');
89
+ }
90
+ break;
91
+ }
92
+ }
93
+ if (!userText.trim()) {
94
+ this.queue.put({
95
+ id: requestId,
96
+ delta: { role: 'assistant', content: "I didn't catch that. Could you repeat?" },
97
+ });
98
+ return;
99
+ }
100
+ console.log(`🎤 User: "${userText.substring(0, 100)}${userText.length > 100 ? '...' : ''}"`);
101
+ // Create or reuse thread
102
+ if (!this.#thread) {
103
+ console.log('🆕 Starting new Codex thread');
104
+ this.#thread = this.#codex.startThread({
105
+ workingDirectory: this.#opts.workingDirectory,
106
+ skipGitRepoCheck: this.#opts.skipGitRepoCheck,
107
+ });
108
+ this.#onThread(this.#thread);
109
+ }
110
+ else {
111
+ console.log('🔄 Continuing Codex thread');
112
+ }
113
+ // Run Codex and get result
114
+ const turn = await this.#thread.run(userText);
115
+ // Emit tool usage events
116
+ if (turn.items && turn.items.length > 0) {
117
+ for (const item of turn.items) {
118
+ console.log(`🔧 Codex: ${item.type || 'action'}`);
119
+ this.#eventEmitter.emit('tool_use', item);
120
+ }
121
+ }
122
+ // Send response to TTS
123
+ const result = turn.finalResponse || 'Done.';
124
+ this.queue.put({
125
+ id: requestId,
126
+ delta: { role: 'assistant', content: result },
127
+ });
128
+ console.log('✅ Codex response complete');
129
+ }
130
+ catch (error) {
131
+ console.error('❌ Codex Agent SDK error:', error);
132
+ this.queue.put({
133
+ id: requestId,
134
+ delta: { role: 'assistant', content: 'Sorry, I encountered an error.' },
135
+ });
136
+ }
137
+ }
138
+ }
139
+ /**
140
+ * Create a CodexLLM instance
141
+ */
142
+ export function createCodexLLM(opts) {
143
+ return new CodexLLM(opts);
144
+ }
package/dist/config.d.ts CHANGED
@@ -1,9 +1,55 @@
1
1
  import type { McpServerConfig } from './claude-handler.js';
2
+ export type VoiceMode = 'direct' | 'realtime';
3
+ export type EditMode = 'read-only' | 'edit';
4
+ export type AgentMode = 'plan' | 'execute' | 'research';
5
+ export type RealtimeProvider = 'openai' | 'gemini';
6
+ export type STTProvider = 'deepgram' | 'groq-whisper' | 'openai-whisper';
7
+ export type TTSProvider = 'gemini' | 'openai' | 'elevenlabs' | 'deepgram';
8
+ export type BridgeLLMProvider = 'gemini-pro' | 'gpt-4o';
9
+ export interface RealtimeConfig {
10
+ provider?: RealtimeProvider;
11
+ openaiVoice?: 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer';
12
+ openaiModel?: string;
13
+ geminiVoice?: 'Aoede' | 'Charon' | 'Kore' | 'Fenrir' | 'Puck';
14
+ geminiModel?: string;
15
+ }
16
+ export interface DirectConfig {
17
+ stt?: {
18
+ provider?: STTProvider;
19
+ model?: string;
20
+ language?: string;
21
+ };
22
+ tts?: {
23
+ provider?: TTSProvider;
24
+ model?: string;
25
+ voice?: string;
26
+ };
27
+ }
28
+ export interface PipelinedConfig {
29
+ stt?: {
30
+ provider?: STTProvider;
31
+ model?: string;
32
+ language?: string;
33
+ };
34
+ llm?: {
35
+ provider?: BridgeLLMProvider;
36
+ model?: string;
37
+ };
38
+ tts?: {
39
+ provider?: TTSProvider;
40
+ model?: string;
41
+ voice?: string;
42
+ };
43
+ }
2
44
  export interface OsbornConfig {
3
45
  workingDirectory?: string;
4
46
  mcpServers?: Record<string, McpServerConfigYaml>;
5
- defaultProvider?: 'openai' | 'gemini';
47
+ defaultProvider?: 'gemini' | 'openai';
6
48
  defaultCodingAgent?: 'claude' | 'codex';
49
+ voiceMode?: VoiceMode;
50
+ realtime?: RealtimeConfig;
51
+ direct?: DirectConfig;
52
+ pipelined?: PipelinedConfig;
7
53
  }
8
54
  interface McpServerConfigYaml {
9
55
  enabled?: boolean;
@@ -13,6 +59,32 @@ interface McpServerConfigYaml {
13
59
  url?: string;
14
60
  transport?: 'stdio' | 'sse' | 'http';
15
61
  }
62
+ export interface McpCatalogEntry {
63
+ name: string;
64
+ description: string;
65
+ serverKey: string;
66
+ category: 'code' | 'web' | 'data' | 'utility';
67
+ command?: string;
68
+ args?: string[];
69
+ env?: Record<string, string>;
70
+ requiredEnvVars?: string[];
71
+ transport?: 'stdio' | 'http' | 'sse';
72
+ url?: string;
73
+ headers?: Record<string, string>;
74
+ requiredHeaders?: string[];
75
+ }
76
+ export declare const MCP_CATALOG: McpCatalogEntry[];
77
+ export interface McpServerStatus {
78
+ serverKey: string;
79
+ name: string;
80
+ description: string;
81
+ category: McpCatalogEntry['category'];
82
+ transport: 'stdio' | 'http' | 'sse';
83
+ enabled: boolean;
84
+ available: boolean;
85
+ missingEnvVars?: string[];
86
+ source: 'catalog' | 'config';
87
+ }
16
88
  /**
17
89
  * Load configuration from ~/.osborn/config.yaml
18
90
  * Creates default config if it doesn't exist
@@ -26,8 +98,162 @@ export declare function getMcpServers(config: OsbornConfig): Record<string, McpS
26
98
  * Get list of enabled MCP server names (for display)
27
99
  */
28
100
  export declare function getEnabledMcpServerNames(config: OsbornConfig): string[];
101
+ /**
102
+ * Get pipelined config with defaults merged
103
+ */
104
+ export declare function getPipelinedConfig(config: OsbornConfig): Required<PipelinedConfig>;
105
+ /**
106
+ * Get voice mode from config
107
+ */
108
+ export declare function getVoiceMode(config: OsbornConfig): VoiceMode;
109
+ /**
110
+ * Get realtime config with defaults merged
111
+ */
112
+ export declare function getRealtimeConfig(config: OsbornConfig): Required<RealtimeConfig>;
113
+ /**
114
+ * Get direct mode config with defaults merged
115
+ */
116
+ export declare function getDirectConfig(config: OsbornConfig): Required<DirectConfig>;
29
117
  /**
30
118
  * Save config to file
31
119
  */
32
120
  export declare function saveConfig(config: OsbornConfig): void;
121
+ /**
122
+ * Get MCP tool patterns for allowedTools based on configured servers
123
+ * Returns wildcard patterns like 'mcp__github__.*' for each enabled server
124
+ */
125
+ export declare function getMcpToolPatterns(config: OsbornConfig): string[];
126
+ /**
127
+ * Get MCP server status list — merges catalog entries with user config.
128
+ * Checks env var availability so the UI can show disabled toggles.
129
+ */
130
+ export declare function getMcpServerStatusList(config: OsbornConfig): McpServerStatus[];
131
+ /**
132
+ * Build McpServerConfig records for a given set of enabled keys.
133
+ * Merges catalog defaults with user config overrides.
134
+ */
135
+ export declare function buildMcpServersForKeys(config: OsbornConfig, enabledKeys: string[]): Record<string, McpServerConfig>;
136
+ /**
137
+ * Session metadata for listing available sessions
138
+ */
139
+ export interface SessionInfo {
140
+ sessionId: string;
141
+ projectPath: string;
142
+ timestamp: Date;
143
+ lastMessage?: string;
144
+ messageCount: number;
145
+ filePath: string;
146
+ }
147
+ /**
148
+ * Get the .claude projects directory
149
+ */
150
+ export declare function getClaudeProjectsDir(): string;
151
+ /**
152
+ * Get the session storage directory for a specific project
153
+ */
154
+ export declare function getSessionDir(projectPath: string): string;
155
+ /**
156
+ * List all available sessions for the current project
157
+ */
158
+ export declare function listSessions(projectPath?: string): Promise<SessionInfo[]>;
159
+ /**
160
+ * Get the most recent session ID for the current project
161
+ */
162
+ export declare function getMostRecentSessionId(projectPath?: string): Promise<string | null>;
163
+ /**
164
+ * Check if a session exists
165
+ */
166
+ export declare function sessionExists(sessionId: string, projectPath?: string): boolean;
167
+ /**
168
+ * Session summary for context briefing when switching sessions
169
+ */
170
+ export interface SessionSummary {
171
+ sessionId: string;
172
+ messageCount: number;
173
+ lastMessages: string[];
174
+ }
175
+ /**
176
+ * Get a summary of a session for context briefing
177
+ * Extracts last few messages and mode info for realtime agent
178
+ */
179
+ export declare function getSessionSummary(sessionId: string, projectPath: string): Promise<SessionSummary | null>;
180
+ /**
181
+ * Represents a single exchange in the conversation
182
+ */
183
+ export interface ConversationExchange {
184
+ role: 'user' | 'assistant';
185
+ content: string;
186
+ timestamp?: string;
187
+ }
188
+ /**
189
+ * Get conversation history from a session file
190
+ * Returns the last N exchanges (user/assistant pairs)
191
+ */
192
+ export declare function getConversationHistory(sessionId: string, projectPath: string, limit?: number): Promise<ConversationExchange[]>;
193
+ /**
194
+ * Session metadata for persisting mode state
195
+ */
196
+ export interface SessionMetadata {
197
+ sessionId: string;
198
+ lastUpdated: string;
199
+ projectPath: string;
200
+ messageCount?: number;
201
+ lastMessages?: string[];
202
+ summary?: string;
203
+ researchDir?: string;
204
+ researchArtifacts?: string[];
205
+ agentMode?: AgentMode;
206
+ editMode?: EditMode;
207
+ }
208
+ /**
209
+ * Save metadata for a specific session
210
+ */
211
+ export declare function saveSessionMetadata(projectPath: string, metadata: SessionMetadata): void;
212
+ /**
213
+ * Get metadata for a specific session
214
+ */
215
+ export declare function getSessionMetadataById(projectPath: string, sessionId: string): SessionMetadata | null;
216
+ /**
217
+ * Get metadata for the most recent session
218
+ */
219
+ export declare function getMostRecentSessionMetadata(projectPath: string): Promise<SessionMetadata | null>;
220
+ /**
221
+ * Delete metadata for a specific session
222
+ */
223
+ export declare function deleteSessionMetadata(projectPath: string, sessionId: string): void;
224
+ /**
225
+ * Clean up metadata for sessions that no longer exist
226
+ */
227
+ export declare function cleanupOrphanedMetadata(projectPath: string): Promise<number>;
228
+ export declare function getSessionWorkspace(projectPath: string, sessionId: string): string;
229
+ export declare function ensureSessionWorkspace(projectPath: string, sessionId: string): string;
230
+ /**
231
+ * Rename a session workspace folder to match the SDK session ID.
232
+ * Returns the new path, or null if rename was not needed/possible.
233
+ */
234
+ export declare function renameSessionWorkspace(projectPath: string, oldSessionId: string, newSessionId: string): string | null;
235
+ export declare function getResearchDir(projectPath: string, sessionId: string): string;
236
+ export declare function ensureResearchDir(projectPath: string, sessionId: string): string;
237
+ /**
238
+ * Read the session spec document (spec.md) if it exists
239
+ */
240
+ export declare function readSessionSpec(projectPath: string, sessionId: string): string | null;
241
+ /**
242
+ * List files in the session library directory
243
+ */
244
+ export declare function listLibraryFiles(projectPath: string, sessionId: string): string[];
245
+ export interface ResearchArtifact {
246
+ fileName: string;
247
+ filePath: string;
248
+ type: 'plan' | 'diagram' | 'notes' | 'image' | 'summary' | 'html' | 'other';
249
+ size: number;
250
+ updatedAt: string;
251
+ }
252
+ export declare function listResearchArtifacts(projectPath: string, sessionId: string): ResearchArtifact[];
253
+ /**
254
+ * List artifacts in a session workspace.
255
+ * When sessionId is provided, scans the per-session folder (.osborn/sessions/{sessionId}/).
256
+ * Without sessionId, falls back to the flat .osborn/sessions/ directory (legacy).
257
+ */
258
+ export declare function listWorkspaceArtifacts(projectPath: string, sessionId?: string): ResearchArtifact[];
33
259
  export {};