@vibeframe/cli 0.27.0 → 0.30.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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agent/adapters/index.d.ts +1 -0
  3. package/dist/agent/adapters/index.d.ts.map +1 -1
  4. package/dist/agent/adapters/index.js +5 -0
  5. package/dist/agent/adapters/index.js.map +1 -1
  6. package/dist/agent/adapters/openrouter.d.ts +16 -0
  7. package/dist/agent/adapters/openrouter.d.ts.map +1 -0
  8. package/dist/agent/adapters/openrouter.js +100 -0
  9. package/dist/agent/adapters/openrouter.js.map +1 -0
  10. package/dist/agent/types.d.ts +1 -1
  11. package/dist/agent/types.d.ts.map +1 -1
  12. package/dist/commands/agent.d.ts.map +1 -1
  13. package/dist/commands/agent.js +3 -1
  14. package/dist/commands/agent.js.map +1 -1
  15. package/dist/commands/ai-edit-cli.d.ts.map +1 -1
  16. package/dist/commands/ai-edit-cli.js +18 -0
  17. package/dist/commands/ai-edit-cli.js.map +1 -1
  18. package/dist/commands/generate.js +14 -0
  19. package/dist/commands/generate.js.map +1 -1
  20. package/dist/commands/schema.d.ts +1 -0
  21. package/dist/commands/schema.d.ts.map +1 -1
  22. package/dist/commands/schema.js +122 -21
  23. package/dist/commands/schema.js.map +1 -1
  24. package/dist/commands/setup.js +5 -2
  25. package/dist/commands/setup.js.map +1 -1
  26. package/dist/config/schema.d.ts +2 -1
  27. package/dist/config/schema.d.ts.map +1 -1
  28. package/dist/config/schema.js +2 -0
  29. package/dist/config/schema.js.map +1 -1
  30. package/dist/index.js +0 -0
  31. package/package.json +16 -12
  32. package/.turbo/turbo-build.log +0 -4
  33. package/.turbo/turbo-lint.log +0 -21
  34. package/.turbo/turbo-test.log +0 -689
  35. package/src/agent/adapters/claude.ts +0 -143
  36. package/src/agent/adapters/gemini.ts +0 -159
  37. package/src/agent/adapters/index.ts +0 -61
  38. package/src/agent/adapters/ollama.ts +0 -231
  39. package/src/agent/adapters/openai.ts +0 -116
  40. package/src/agent/adapters/xai.ts +0 -119
  41. package/src/agent/index.ts +0 -251
  42. package/src/agent/memory/index.ts +0 -151
  43. package/src/agent/prompts/system.ts +0 -106
  44. package/src/agent/tools/ai-editing.ts +0 -845
  45. package/src/agent/tools/ai-generation.ts +0 -1073
  46. package/src/agent/tools/ai-pipeline.ts +0 -1055
  47. package/src/agent/tools/ai.ts +0 -21
  48. package/src/agent/tools/batch.ts +0 -429
  49. package/src/agent/tools/e2e.test.ts +0 -545
  50. package/src/agent/tools/export.ts +0 -184
  51. package/src/agent/tools/filesystem.ts +0 -237
  52. package/src/agent/tools/index.ts +0 -150
  53. package/src/agent/tools/integration.test.ts +0 -775
  54. package/src/agent/tools/media.ts +0 -697
  55. package/src/agent/tools/project.ts +0 -313
  56. package/src/agent/tools/timeline.ts +0 -951
  57. package/src/agent/types.ts +0 -68
  58. package/src/commands/agent.ts +0 -340
  59. package/src/commands/ai-analyze.ts +0 -429
  60. package/src/commands/ai-animated-caption.ts +0 -390
  61. package/src/commands/ai-audio.ts +0 -941
  62. package/src/commands/ai-broll.ts +0 -490
  63. package/src/commands/ai-edit-cli.ts +0 -658
  64. package/src/commands/ai-edit.ts +0 -1542
  65. package/src/commands/ai-fill-gaps.ts +0 -566
  66. package/src/commands/ai-helpers.ts +0 -65
  67. package/src/commands/ai-highlights.ts +0 -1303
  68. package/src/commands/ai-image.ts +0 -761
  69. package/src/commands/ai-motion.ts +0 -347
  70. package/src/commands/ai-narrate.ts +0 -451
  71. package/src/commands/ai-review.ts +0 -309
  72. package/src/commands/ai-script-pipeline-cli.ts +0 -1710
  73. package/src/commands/ai-script-pipeline.ts +0 -1365
  74. package/src/commands/ai-suggest-edit.ts +0 -264
  75. package/src/commands/ai-video-fx.ts +0 -445
  76. package/src/commands/ai-video.ts +0 -915
  77. package/src/commands/ai-viral.ts +0 -595
  78. package/src/commands/ai-visual-fx.ts +0 -601
  79. package/src/commands/ai.test.ts +0 -627
  80. package/src/commands/ai.ts +0 -307
  81. package/src/commands/analyze.ts +0 -282
  82. package/src/commands/audio.ts +0 -644
  83. package/src/commands/batch.test.ts +0 -279
  84. package/src/commands/batch.ts +0 -440
  85. package/src/commands/detect.ts +0 -329
  86. package/src/commands/doctor.ts +0 -237
  87. package/src/commands/edit-cmd.ts +0 -1014
  88. package/src/commands/export.ts +0 -918
  89. package/src/commands/generate.ts +0 -2146
  90. package/src/commands/media.ts +0 -177
  91. package/src/commands/output.ts +0 -142
  92. package/src/commands/pipeline.ts +0 -398
  93. package/src/commands/project.test.ts +0 -127
  94. package/src/commands/project.ts +0 -149
  95. package/src/commands/sanitize.ts +0 -60
  96. package/src/commands/schema.ts +0 -130
  97. package/src/commands/setup.ts +0 -509
  98. package/src/commands/timeline.test.ts +0 -499
  99. package/src/commands/timeline.ts +0 -529
  100. package/src/commands/validate.ts +0 -77
  101. package/src/config/config.test.ts +0 -197
  102. package/src/config/index.ts +0 -125
  103. package/src/config/schema.ts +0 -82
  104. package/src/engine/index.ts +0 -2
  105. package/src/engine/project.test.ts +0 -702
  106. package/src/engine/project.ts +0 -439
  107. package/src/index.ts +0 -146
  108. package/src/utils/api-key.test.ts +0 -41
  109. package/src/utils/api-key.ts +0 -247
  110. package/src/utils/audio.ts +0 -83
  111. package/src/utils/exec-safe.ts +0 -75
  112. package/src/utils/first-run.ts +0 -52
  113. package/src/utils/provider-resolver.ts +0 -56
  114. package/src/utils/remotion.ts +0 -951
  115. package/src/utils/subtitle.test.ts +0 -227
  116. package/src/utils/subtitle.ts +0 -169
  117. package/src/utils/tty.ts +0 -196
  118. package/tsconfig.json +0 -20
@@ -1,143 +0,0 @@
1
- /**
2
- * Claude LLM Adapter with tool_use
3
- */
4
-
5
- import Anthropic from "@anthropic-ai/sdk";
6
- import type { LLMAdapter } from "./index.js";
7
- import type {
8
- ToolDefinition,
9
- LLMResponse,
10
- AgentMessage,
11
- ToolCall,
12
- LLMProvider,
13
- } from "../types.js";
14
-
15
- export class ClaudeAdapter implements LLMAdapter {
16
- readonly provider: LLMProvider = "claude";
17
- private client: Anthropic | null = null;
18
- private model: string = "claude-sonnet-4-6";
19
-
20
- async initialize(apiKey: string): Promise<void> {
21
- this.client = new Anthropic({ apiKey });
22
- }
23
-
24
- isInitialized(): boolean {
25
- return this.client !== null;
26
- }
27
-
28
- setModel(model: string): void {
29
- this.model = model;
30
- }
31
-
32
- async chat(
33
- messages: AgentMessage[],
34
- tools: ToolDefinition[]
35
- ): Promise<LLMResponse> {
36
- if (!this.client) {
37
- throw new Error("Claude adapter not initialized");
38
- }
39
-
40
- // Extract system message
41
- const systemMessage = messages.find((m) => m.role === "system");
42
- const systemPrompt = systemMessage?.content || "";
43
-
44
- // Convert messages to Claude format
45
- const claudeMessages: Anthropic.MessageParam[] = [];
46
-
47
- for (const msg of messages) {
48
- if (msg.role === "system") continue;
49
-
50
- if (msg.role === "user") {
51
- claudeMessages.push({
52
- role: "user",
53
- content: msg.content,
54
- });
55
- } else if (msg.role === "assistant") {
56
- const content: Anthropic.ContentBlockParam[] = [];
57
-
58
- if (msg.content) {
59
- content.push({ type: "text", text: msg.content });
60
- }
61
-
62
- if (msg.toolCalls) {
63
- for (const tc of msg.toolCalls) {
64
- content.push({
65
- type: "tool_use",
66
- id: tc.id,
67
- name: tc.name,
68
- input: tc.arguments,
69
- });
70
- }
71
- }
72
-
73
- if (content.length > 0) {
74
- claudeMessages.push({
75
- role: "assistant",
76
- content,
77
- });
78
- }
79
- } else if (msg.role === "tool") {
80
- claudeMessages.push({
81
- role: "user",
82
- content: [
83
- {
84
- type: "tool_result",
85
- tool_use_id: msg.toolCallId!,
86
- content: msg.content,
87
- },
88
- ],
89
- });
90
- }
91
- }
92
-
93
- // Convert tools to Claude format
94
- const claudeTools: Anthropic.Tool[] = tools.map((tool) => ({
95
- name: tool.name,
96
- description: tool.description,
97
- input_schema: {
98
- type: "object" as const,
99
- properties: tool.parameters.properties as Record<string, unknown>,
100
- required: tool.parameters.required,
101
- },
102
- }));
103
-
104
- // Make API call
105
- const response = await this.client.messages.create({
106
- model: this.model,
107
- max_tokens: 4096,
108
- system: systemPrompt,
109
- messages: claudeMessages,
110
- tools: claudeTools.length > 0 ? claudeTools : undefined,
111
- });
112
-
113
- // Parse response
114
- let textContent = "";
115
- const toolCalls: ToolCall[] = [];
116
-
117
- for (const block of response.content) {
118
- if (block.type === "text") {
119
- textContent += block.text;
120
- } else if (block.type === "tool_use") {
121
- toolCalls.push({
122
- id: block.id,
123
- name: block.name,
124
- arguments: block.input as Record<string, unknown>,
125
- });
126
- }
127
- }
128
-
129
- // Map stop reason
130
- let finishReason: LLMResponse["finishReason"] = "stop";
131
- if (response.stop_reason === "tool_use") {
132
- finishReason = "tool_calls";
133
- } else if (response.stop_reason === "max_tokens") {
134
- finishReason = "length";
135
- }
136
-
137
- return {
138
- content: textContent,
139
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
140
- finishReason,
141
- };
142
- }
143
- }
@@ -1,159 +0,0 @@
1
- /**
2
- * Gemini LLM Adapter with Function Calling
3
- */
4
-
5
- import { GoogleGenerativeAI, SchemaType, type Content, type Part, type Tool as GeminiTool, type FunctionDeclarationSchemaProperty } from "@google/generative-ai";
6
- import type { LLMAdapter } from "./index.js";
7
- import type {
8
- ToolDefinition,
9
- LLMResponse,
10
- AgentMessage,
11
- ToolCall,
12
- LLMProvider,
13
- } from "../types.js";
14
-
15
- export class GeminiAdapter implements LLMAdapter {
16
- readonly provider: LLMProvider = "gemini";
17
- private client: GoogleGenerativeAI | null = null;
18
- private model: string = "gemini-2.5-flash";
19
-
20
- async initialize(apiKey: string): Promise<void> {
21
- this.client = new GoogleGenerativeAI(apiKey);
22
- }
23
-
24
- isInitialized(): boolean {
25
- return this.client !== null;
26
- }
27
-
28
- setModel(model: string): void {
29
- this.model = model;
30
- }
31
-
32
- async chat(
33
- messages: AgentMessage[],
34
- tools: ToolDefinition[]
35
- ): Promise<LLMResponse> {
36
- if (!this.client) {
37
- throw new Error("Gemini adapter not initialized");
38
- }
39
-
40
- // Extract system message
41
- const systemMessage = messages.find((m) => m.role === "system");
42
- const systemInstruction = systemMessage?.content;
43
-
44
- // Convert tools to Gemini format
45
- const geminiTools: GeminiTool[] = tools.length > 0 ? [{
46
- functionDeclarations: tools.map((tool) => ({
47
- name: tool.name,
48
- description: tool.description,
49
- parameters: {
50
- type: SchemaType.OBJECT,
51
- properties: tool.parameters.properties as Record<string, FunctionDeclarationSchemaProperty>,
52
- required: tool.parameters.required,
53
- },
54
- })),
55
- }] : [];
56
-
57
- // Get model with tools
58
- const model = this.client.getGenerativeModel({
59
- model: this.model,
60
- systemInstruction,
61
- tools: geminiTools.length > 0 ? geminiTools : undefined,
62
- });
63
-
64
- // Convert messages to Gemini format
65
- const geminiContents: Content[] = [];
66
-
67
- for (const msg of messages) {
68
- if (msg.role === "system") continue;
69
-
70
- if (msg.role === "user") {
71
- geminiContents.push({
72
- role: "user",
73
- parts: [{ text: msg.content }],
74
- });
75
- } else if (msg.role === "assistant") {
76
- const parts: Part[] = [];
77
-
78
- if (msg.content) {
79
- parts.push({ text: msg.content });
80
- }
81
-
82
- if (msg.toolCalls) {
83
- for (const tc of msg.toolCalls) {
84
- parts.push({
85
- functionCall: {
86
- name: tc.name,
87
- args: tc.arguments,
88
- },
89
- });
90
- }
91
- }
92
-
93
- if (parts.length > 0) {
94
- geminiContents.push({
95
- role: "model",
96
- parts,
97
- });
98
- }
99
- } else if (msg.role === "tool") {
100
- // Gemini expects function responses in user turn
101
- geminiContents.push({
102
- role: "user",
103
- parts: [{
104
- functionResponse: {
105
- name: msg.toolCallId!.split(":::")[0] || "unknown", // Extract function name from id
106
- response: { result: msg.content },
107
- },
108
- }],
109
- });
110
- }
111
- }
112
-
113
- // Make API call
114
- const result = await model.generateContent({
115
- contents: geminiContents,
116
- });
117
-
118
- const response = result.response;
119
- const candidate = response.candidates?.[0];
120
-
121
- if (!candidate) {
122
- return {
123
- content: "",
124
- finishReason: "error",
125
- };
126
- }
127
-
128
- // Parse response
129
- let textContent = "";
130
- const toolCalls: ToolCall[] = [];
131
-
132
- for (const part of candidate.content.parts) {
133
- if ("text" in part && part.text) {
134
- textContent += part.text;
135
- } else if ("functionCall" in part && part.functionCall) {
136
- const fc = part.functionCall;
137
- toolCalls.push({
138
- id: `${fc.name}:::${Date.now()}`,
139
- name: fc.name,
140
- arguments: (fc.args || {}) as Record<string, unknown>,
141
- });
142
- }
143
- }
144
-
145
- // Map finish reason
146
- let finishReason: LLMResponse["finishReason"] = "stop";
147
- if (toolCalls.length > 0) {
148
- finishReason = "tool_calls";
149
- } else if (candidate.finishReason === "MAX_TOKENS") {
150
- finishReason = "length";
151
- }
152
-
153
- return {
154
- content: textContent,
155
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
156
- finishReason,
157
- };
158
- }
159
- }
@@ -1,61 +0,0 @@
1
- /**
2
- * LLM Adapter Interface and Factory
3
- */
4
-
5
- import type { ToolDefinition, LLMResponse, AgentMessage, LLMProvider } from "../types.js";
6
-
7
- /**
8
- * Abstract interface for LLM providers
9
- */
10
- export interface LLMAdapter {
11
- /** Provider name */
12
- readonly provider: LLMProvider;
13
-
14
- /** Initialize the adapter with API key */
15
- initialize(apiKey: string): Promise<void>;
16
-
17
- /** Check if adapter is initialized */
18
- isInitialized(): boolean;
19
-
20
- /** Send messages with tools and get response */
21
- chat(
22
- messages: AgentMessage[],
23
- tools: ToolDefinition[]
24
- ): Promise<LLMResponse>;
25
- }
26
-
27
- /**
28
- * Factory for creating LLM adapters
29
- */
30
- export async function createAdapter(provider: LLMProvider): Promise<LLMAdapter> {
31
- switch (provider) {
32
- case "openai": {
33
- const { OpenAIAdapter } = await import("./openai.js");
34
- return new OpenAIAdapter();
35
- }
36
- case "claude": {
37
- const { ClaudeAdapter } = await import("./claude.js");
38
- return new ClaudeAdapter();
39
- }
40
- case "gemini": {
41
- const { GeminiAdapter } = await import("./gemini.js");
42
- return new GeminiAdapter();
43
- }
44
- case "ollama": {
45
- const { OllamaAdapter } = await import("./ollama.js");
46
- return new OllamaAdapter();
47
- }
48
- case "xai": {
49
- const { XAIAdapter } = await import("./xai.js");
50
- return new XAIAdapter();
51
- }
52
- default:
53
- throw new Error(`Unknown provider: ${provider}`);
54
- }
55
- }
56
-
57
- export { OpenAIAdapter } from "./openai.js";
58
- export { ClaudeAdapter } from "./claude.js";
59
- export { GeminiAdapter } from "./gemini.js";
60
- export { OllamaAdapter } from "./ollama.js";
61
- export { XAIAdapter } from "./xai.js";
@@ -1,231 +0,0 @@
1
- /**
2
- * Ollama LLM Adapter with JSON-based tool calling
3
- * Uses prompt engineering to simulate tool calling for local models
4
- */
5
-
6
- import type { LLMAdapter } from "./index.js";
7
- import type {
8
- ToolDefinition,
9
- LLMResponse,
10
- AgentMessage,
11
- ToolCall,
12
- LLMProvider,
13
- } from "../types.js";
14
-
15
- interface OllamaResponse {
16
- model: string;
17
- created_at: string;
18
- message: {
19
- role: string;
20
- content: string;
21
- };
22
- done: boolean;
23
- }
24
-
25
- export class OllamaAdapter implements LLMAdapter {
26
- readonly provider: LLMProvider = "ollama";
27
- private baseUrl: string = "http://localhost:11434";
28
- private model: string = "llama3.2";
29
- private initialized: boolean = false;
30
-
31
- async initialize(apiKey: string): Promise<void> {
32
- // apiKey is ignored for Ollama, but we use it to set custom URL if provided
33
- if (apiKey && apiKey.startsWith("http")) {
34
- this.baseUrl = apiKey;
35
- }
36
- this.initialized = true;
37
- }
38
-
39
- isInitialized(): boolean {
40
- return this.initialized;
41
- }
42
-
43
- setModel(model: string): void {
44
- this.model = model;
45
- }
46
-
47
- setBaseUrl(url: string): void {
48
- this.baseUrl = url;
49
- }
50
-
51
- async chat(
52
- messages: AgentMessage[],
53
- tools: ToolDefinition[]
54
- ): Promise<LLMResponse> {
55
- if (!this.initialized) {
56
- throw new Error("Ollama adapter not initialized");
57
- }
58
-
59
- // Build system prompt with tool definitions
60
- const systemMessage = messages.find((m) => m.role === "system");
61
- let systemPrompt = systemMessage?.content || "";
62
-
63
- if (tools.length > 0) {
64
- systemPrompt += "\n\n## IMPORTANT: Tool Usage Instructions\n";
65
- systemPrompt += "You have access to tools that you MUST use to complete tasks. Do NOT describe how to do something - USE THE TOOLS.\n\n";
66
- systemPrompt += "To call a tool, respond with ONLY a JSON object in this exact format:\n";
67
- systemPrompt += '```json\n{"tool_calls": [{"name": "tool_name", "arguments": {"param": "value"}}]}\n```\n\n';
68
- systemPrompt += "Example - If the user asks to list files:\n";
69
- systemPrompt += '```json\n{"tool_calls": [{"name": "fs_list", "arguments": {"path": "."}}]}\n```\n\n';
70
- systemPrompt += "Example - If the user asks to create a project:\n";
71
- systemPrompt += '```json\n{"tool_calls": [{"name": "project_create", "arguments": {"name": "my-project"}}]}\n```\n\n';
72
- systemPrompt += "RULES:\n";
73
- systemPrompt += "1. When asked to DO something, ALWAYS use the appropriate tool\n";
74
- systemPrompt += "2. Do NOT explain how to use terminal commands - use tools instead\n";
75
- systemPrompt += "3. After tool results, summarize what was done\n\n";
76
- systemPrompt += "## Available Tools:\n";
77
-
78
- for (const tool of tools) {
79
- systemPrompt += `\n### ${tool.name}\n`;
80
- systemPrompt += `${tool.description}\n`;
81
- const params = tool.parameters.properties;
82
- const required = tool.parameters.required || [];
83
- if (Object.keys(params).length > 0) {
84
- systemPrompt += `Parameters:\n`;
85
- for (const [name, param] of Object.entries(params)) {
86
- const req = required.includes(name) ? "(required)" : "(optional)";
87
- systemPrompt += ` - ${name} ${req}: ${(param as { description?: string }).description || ""}\n`;
88
- }
89
- }
90
- }
91
- }
92
-
93
- // Convert messages to Ollama format
94
- const ollamaMessages: { role: string; content: string }[] = [];
95
-
96
- // Add system message first
97
- ollamaMessages.push({
98
- role: "system",
99
- content: systemPrompt,
100
- });
101
-
102
- for (const msg of messages) {
103
- if (msg.role === "system") continue;
104
-
105
- if (msg.role === "user") {
106
- ollamaMessages.push({
107
- role: "user",
108
- content: msg.content,
109
- });
110
- } else if (msg.role === "assistant") {
111
- let content = msg.content;
112
-
113
- // Include tool calls in content for context
114
- if (msg.toolCalls) {
115
- content += `\n\nTool calls made:\n${JSON.stringify({ tool_calls: msg.toolCalls }, null, 2)}`;
116
- }
117
-
118
- ollamaMessages.push({
119
- role: "assistant",
120
- content,
121
- });
122
- } else if (msg.role === "tool") {
123
- // Include tool results as user messages for context
124
- ollamaMessages.push({
125
- role: "user",
126
- content: `Tool result (${msg.toolCallId}):\n${msg.content}`,
127
- });
128
- }
129
- }
130
-
131
- // Make API call
132
- const response = await fetch(`${this.baseUrl}/api/chat`, {
133
- method: "POST",
134
- headers: {
135
- "Content-Type": "application/json",
136
- },
137
- body: JSON.stringify({
138
- model: this.model,
139
- messages: ollamaMessages,
140
- stream: false,
141
- }),
142
- });
143
-
144
- if (!response.ok) {
145
- throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
146
- }
147
-
148
- const data = (await response.json()) as OllamaResponse;
149
- const content = data.message.content;
150
-
151
- // Try to parse tool calls from response
152
- const toolCalls = this.parseToolCalls(content);
153
-
154
- if (toolCalls.length > 0) {
155
- // Extract text content before the JSON
156
- const textContent = this.extractTextBeforeJson(content);
157
-
158
- return {
159
- content: textContent,
160
- toolCalls,
161
- finishReason: "tool_calls",
162
- };
163
- }
164
-
165
- return {
166
- content,
167
- finishReason: "stop",
168
- };
169
- }
170
-
171
- private parseToolCalls(content: string): ToolCall[] {
172
- const toolCalls: ToolCall[] = [];
173
-
174
- // Try to find JSON block with tool_calls
175
- const jsonMatch = content.match(/```json\s*(\{[\s\S]*?\})\s*```/);
176
- if (jsonMatch) {
177
- try {
178
- const parsed = JSON.parse(jsonMatch[1]);
179
- if (parsed.tool_calls && Array.isArray(parsed.tool_calls)) {
180
- for (const tc of parsed.tool_calls) {
181
- toolCalls.push({
182
- id: `ollama-${Date.now()}-${Math.random().toString(36).slice(2)}`,
183
- name: tc.name,
184
- arguments: tc.arguments || {},
185
- });
186
- }
187
- }
188
- } catch {
189
- // Not valid JSON
190
- }
191
- }
192
-
193
- // Also try to find raw JSON object
194
- if (toolCalls.length === 0) {
195
- const rawJsonMatch = content.match(/\{"tool_calls":\s*\[[\s\S]*?\]\}/);
196
- if (rawJsonMatch) {
197
- try {
198
- const parsed = JSON.parse(rawJsonMatch[0]);
199
- if (parsed.tool_calls && Array.isArray(parsed.tool_calls)) {
200
- for (const tc of parsed.tool_calls) {
201
- toolCalls.push({
202
- id: `ollama-${Date.now()}-${Math.random().toString(36).slice(2)}`,
203
- name: tc.name,
204
- arguments: tc.arguments || {},
205
- });
206
- }
207
- }
208
- } catch {
209
- // Not valid JSON
210
- }
211
- }
212
- }
213
-
214
- return toolCalls;
215
- }
216
-
217
- private extractTextBeforeJson(content: string): string {
218
- // Find the start of JSON block
219
- const jsonStart = content.indexOf("```json");
220
- if (jsonStart > 0) {
221
- return content.substring(0, jsonStart).trim();
222
- }
223
-
224
- const rawJsonStart = content.indexOf('{"tool_calls"');
225
- if (rawJsonStart > 0) {
226
- return content.substring(0, rawJsonStart).trim();
227
- }
228
-
229
- return "";
230
- }
231
- }
@@ -1,116 +0,0 @@
1
- /**
2
- * OpenAI LLM Adapter with Function Calling
3
- */
4
-
5
- import OpenAI from "openai";
6
- import type { LLMAdapter } from "./index.js";
7
- import type {
8
- ToolDefinition,
9
- LLMResponse,
10
- AgentMessage,
11
- ToolCall,
12
- LLMProvider,
13
- } from "../types.js";
14
-
15
- export class OpenAIAdapter implements LLMAdapter {
16
- readonly provider: LLMProvider = "openai";
17
- private client: OpenAI | null = null;
18
- private model: string = "gpt-5-mini";
19
-
20
- async initialize(apiKey: string): Promise<void> {
21
- this.client = new OpenAI({ apiKey });
22
- }
23
-
24
- isInitialized(): boolean {
25
- return this.client !== null;
26
- }
27
-
28
- setModel(model: string): void {
29
- this.model = model;
30
- }
31
-
32
- async chat(
33
- messages: AgentMessage[],
34
- tools: ToolDefinition[]
35
- ): Promise<LLMResponse> {
36
- if (!this.client) {
37
- throw new Error("OpenAI adapter not initialized");
38
- }
39
-
40
- // Convert messages to OpenAI format
41
- const openaiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = messages.map(
42
- (msg) => {
43
- if (msg.role === "tool") {
44
- return {
45
- role: "tool" as const,
46
- tool_call_id: msg.toolCallId!,
47
- content: msg.content,
48
- };
49
- }
50
- if (msg.role === "assistant" && msg.toolCalls) {
51
- return {
52
- role: "assistant" as const,
53
- content: msg.content || null,
54
- tool_calls: msg.toolCalls.map((tc) => ({
55
- id: tc.id,
56
- type: "function" as const,
57
- function: {
58
- name: tc.name,
59
- arguments: JSON.stringify(tc.arguments),
60
- },
61
- })),
62
- };
63
- }
64
- return {
65
- role: msg.role as "system" | "user" | "assistant",
66
- content: msg.content,
67
- };
68
- }
69
- );
70
-
71
- // Convert tools to OpenAI format
72
- const openaiTools: OpenAI.Chat.ChatCompletionTool[] = tools.map((tool) => ({
73
- type: "function" as const,
74
- function: {
75
- name: tool.name,
76
- description: tool.description,
77
- parameters: tool.parameters as unknown as Record<string, unknown>,
78
- },
79
- }));
80
-
81
- // Make API call
82
- const response = await this.client.chat.completions.create({
83
- model: this.model,
84
- messages: openaiMessages,
85
- tools: openaiTools.length > 0 ? openaiTools : undefined,
86
- tool_choice: openaiTools.length > 0 ? "auto" : undefined,
87
- });
88
-
89
- const choice = response.choices[0];
90
- const message = choice.message;
91
-
92
- // Parse tool calls
93
- let toolCalls: ToolCall[] | undefined;
94
- if (message.tool_calls && message.tool_calls.length > 0) {
95
- toolCalls = message.tool_calls.map((tc) => ({
96
- id: tc.id,
97
- name: tc.function.name,
98
- arguments: JSON.parse(tc.function.arguments),
99
- }));
100
- }
101
-
102
- // Map finish reason
103
- let finishReason: LLMResponse["finishReason"] = "stop";
104
- if (choice.finish_reason === "tool_calls") {
105
- finishReason = "tool_calls";
106
- } else if (choice.finish_reason === "length") {
107
- finishReason = "length";
108
- }
109
-
110
- return {
111
- content: message.content || "",
112
- toolCalls,
113
- finishReason,
114
- };
115
- }
116
- }