lemura 1.0.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 (62) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/LICENSE +21 -0
  3. package/README.md +143 -0
  4. package/dist/adapters/index.d.mts +45 -0
  5. package/dist/adapters/index.d.ts +45 -0
  6. package/dist/adapters/index.js +371 -0
  7. package/dist/adapters/index.js.map +1 -0
  8. package/dist/adapters/index.mjs +369 -0
  9. package/dist/adapters/index.mjs.map +1 -0
  10. package/dist/adapters-BSkhv5ac.d.ts +208 -0
  11. package/dist/adapters-BnG0LEYD.d.mts +208 -0
  12. package/dist/context/index.d.mts +143 -0
  13. package/dist/context/index.d.ts +143 -0
  14. package/dist/context/index.js +321 -0
  15. package/dist/context/index.js.map +1 -0
  16. package/dist/context/index.mjs +314 -0
  17. package/dist/context/index.mjs.map +1 -0
  18. package/dist/index.d.mts +91 -0
  19. package/dist/index.d.ts +91 -0
  20. package/dist/index.js +1375 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/index.mjs +1348 -0
  23. package/dist/index.mjs.map +1 -0
  24. package/dist/logger/index.d.mts +19 -0
  25. package/dist/logger/index.d.ts +19 -0
  26. package/dist/logger/index.js +67 -0
  27. package/dist/logger/index.js.map +1 -0
  28. package/dist/logger/index.mjs +65 -0
  29. package/dist/logger/index.mjs.map +1 -0
  30. package/dist/logger-DxvKliuk.d.mts +37 -0
  31. package/dist/logger-DxvKliuk.d.ts +37 -0
  32. package/dist/rag/index.d.mts +10 -0
  33. package/dist/rag/index.d.ts +10 -0
  34. package/dist/rag/index.js +43 -0
  35. package/dist/rag/index.js.map +1 -0
  36. package/dist/rag/index.mjs +41 -0
  37. package/dist/rag/index.mjs.map +1 -0
  38. package/dist/rag-La_Bo-J8.d.mts +45 -0
  39. package/dist/rag-La_Bo-J8.d.ts +45 -0
  40. package/dist/skills/index.d.mts +15 -0
  41. package/dist/skills/index.d.ts +15 -0
  42. package/dist/skills/index.js +40 -0
  43. package/dist/skills/index.js.map +1 -0
  44. package/dist/skills/index.mjs +38 -0
  45. package/dist/skills/index.mjs.map +1 -0
  46. package/dist/skills-wc8S-OvC.d.mts +14 -0
  47. package/dist/skills-wc8S-OvC.d.ts +14 -0
  48. package/dist/storage-BGu4Loao.d.ts +121 -0
  49. package/dist/storage-DMcliVVj.d.mts +121 -0
  50. package/dist/tools/index.d.mts +17 -0
  51. package/dist/tools/index.d.ts +17 -0
  52. package/dist/tools/index.js +72 -0
  53. package/dist/tools/index.js.map +1 -0
  54. package/dist/tools/index.mjs +70 -0
  55. package/dist/tools/index.mjs.map +1 -0
  56. package/dist/types/index.d.mts +118 -0
  57. package/dist/types/index.d.ts +118 -0
  58. package/dist/types/index.js +84 -0
  59. package/dist/types/index.js.map +1 -0
  60. package/dist/types/index.mjs +74 -0
  61. package/dist/types/index.mjs.map +1 -0
  62. package/package.json +79 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2026-03-07
9
+
10
+ ### Added
11
+ - Structured logging system with colors and severity levels (FATAL, ERROR, WARN, INFO, DEBUG).
12
+ - Integrated logging into `SessionManager` and `OpenAICompatibleAdapter`.
13
+ - Added `problem` and `hints` to `LemuraError` for better end-user feedback.
14
+ - Dedicated `ILogger` interface and `DefaultLogger` implementation.
15
+ - Short Term Memory (STM) system for persistent memory across session boundaries
16
+ - Scratchpad tools for managing agent's internal reasoning state
17
+ - `ShortTermMemoryRegistry` for memory item lifecycle management
18
+ - `summarize_sandwich` tool for context compression using sandwich strategy
19
+
20
+ ### Changed
21
+ - `SessionManager` — Core ReAct runtime integration entry point, now integrated with logging.
22
+ - `OpenAICompatibleAdapter` — Reference adapter for OpenAI, now integrated with logging.
23
+
24
+ ### Added (Core)
25
+ - `OpenAICompatibleAdapter` — Reference adapter for OpenAI and standard API-compatible providers.
26
+ - `ContextManager` — Core context logic coordinating multiple string reduction behaviors.
27
+ - `SandwichCompressionStrategy` — Strategy for preserving recency and foundation context.
28
+ - `HistoryCompressionStrategy` — Strategy for compressing history via summarization.
29
+ - `SessionManager` — Core ReAct runtime integration entry point.
30
+ - `ToolRegistry` — Standardized tooling implementation.
31
+ - `SkillInjector` — Advanced dynamic system prompting mechanism.
32
+ - `ToolResponseProcessor` — Evaluates and handles heavy responses.
33
+ - `ContinuationPlanner` — Multi-step sequential tool handling abstraction.
34
+ - `InMemoryRAGAdapter` — Minimal in-memory document ingestion/query layer.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rzafiamy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ <img src="./docs/assets/logo.png" alt="lemura logo" width="200" />
2
+
3
+ # lemura
4
+
5
+ **A provider-agnostic, premium agentic AI runtime for the modern web.**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/lemura.svg?style=flat-square)](https://www.npmjs.com/package/lemura)
8
+ [![license](https://img.shields.io/npm/l/lemura.svg?style=flat-square)](https://github.com/rzafiamy/lemura/blob/main/LICENSE)
9
+ [![build](https://img.shields.io/github/actions/workflow/status/lemura-ai/lemura/ci.yml?branch=main&style=flat-square)](https://github.com/rzafiamy/lemura/actions)
10
+ [![coverage](https://img.shields.io/codecov/c/github/lemura-ai/lemura?style=flat-square)](https://codecov.io/gh/lemura-ai/lemura)
11
+
12
+ ---
13
+
14
+ `lemura` is a robust, provider-agnostic npm package designed to encapsulate a full agentic AI runtime. It simplifies the complex orchestration of LLMs, tools, and context management into a single, cohesive interface.
15
+
16
+ ## 🚀 Install
17
+
18
+ ```bash
19
+ pnpm add lemura
20
+ # or
21
+ npm install lemura
22
+ ```
23
+
24
+ ## ⚙️ Environment Variables
25
+
26
+ The built-in `OpenAICompatibleAdapter` can be configured using environment variables. To load them from a `.env` file in Node.js, you'll typically need a library like `dotenv`:
27
+
28
+ ```bash
29
+ npm install dotenv
30
+ ```
31
+
32
+ Then at the very top of your entry point:
33
+
34
+ ```ts
35
+ import 'dotenv/config';
36
+ ```
37
+
38
+ Create a `.env` file in your project root:
39
+
40
+ ```ini
41
+ # Provider Configuration (OpenAI, Groq, Together, Ollama, etc.)
42
+ LEMURA_API_KEY=your_api_key_here
43
+ LEMURA_BASE_URL=https://api.openai.com/v1
44
+ LEMURA_MODEL=gpt-4o-mini
45
+
46
+ # Fallbacks (Lemura also checks standard OpenAI variables)
47
+ OPENAI_API_KEY=your_api_key_here
48
+ OPENAI_BASE_URL=https://api.openai.com/v1
49
+ OPENAI_MODEL=gpt-4o-mini
50
+ ```
51
+
52
+ ## ⚡ Quick Start
53
+
54
+ ```ts
55
+ import { SessionManager, OpenAICompatibleAdapter } from 'lemura';
56
+
57
+ async function main() {
58
+ const adapter = new OpenAICompatibleAdapter({
59
+ baseUrl: 'https://api.openai.com/v1',
60
+ apiKey: process.env.OPENAI_API_KEY || '',
61
+ defaultModel: 'gpt-4o-mini'
62
+ });
63
+
64
+ const session = new SessionManager({
65
+ adapter,
66
+ model: 'gpt-4o-mini',
67
+ maxTokens: 100000,
68
+ });
69
+
70
+ const response = await session.run('What is lemura?');
71
+ console.log(response);
72
+ }
73
+
74
+ main();
75
+ ```
76
+
77
+ ## 🧠 Core Concepts
78
+
79
+ Explore the architecture and advanced capabilities of `lemura`:
80
+
81
+ - 🏁 [**Getting Started**](./docs/guides/getting-started.md) — Fundamental setup and concepts.
82
+ - 🧹 [**Context Management**](./docs/guides/context-management.md) — Advanced compression strategies.
83
+ - 🔌 [**Adapters**](./docs/guides/adapters.md) — Connecting to OpenAI, Groq, Anthropic, and more.
84
+ - 🛠️ [**Tools and Skills**](./docs/guides/tools-and-skills.md) — Extending agent capabilities.
85
+ - ⚡ [**Advanced Execution**](./docs/guides/advanced-execution.md) — Goal planning and continuation.
86
+
87
+ ## 📦 API Overview
88
+
89
+ | Export | Description |
90
+ |---|---|
91
+ | `SessionManager` | The main entry point orchestrating the ReAct loop and tools. |
92
+ | `ContextManager` | Manages the conversation history using compression strategies. |
93
+ | `OpenAICompatibleAdapter` | Reference adapter for OpenAI, Groq, Together, etc. |
94
+ | `ToolRegistry` | Registers and executes tools for the agent. |
95
+ | `SkillInjector` | Loads and formats YAML/Markdown skills into system prompts. |
96
+ | `DefaultLogger` | Colorized logger with Problem/Hints metadata support. |
97
+
98
+ ## 🪵 Logging and Tracing
99
+
100
+ `lemura` features a premium, structured logging system designed for developer experience. It provides colorized output and actionable hints for errors.
101
+
102
+ ```ts
103
+ import { SessionManager, DefaultLogger, LogLevel } from 'lemura';
104
+
105
+ const logger = new DefaultLogger();
106
+ logger.setLevel(LogLevel.DEBUG); // Set to show trace-level information
107
+
108
+ const session = new SessionManager({
109
+ adapter,
110
+ model: 'gpt-4o-mini',
111
+ maxTokens: 100000,
112
+ logger: logger // Inject the logger
113
+ });
114
+ ```
115
+
116
+ When an error occurs (like an invalid API key), `lemura` provides beautiful, structured feedback:
117
+
118
+ ```text
119
+ 2026-03-07T13:05:49.686Z [FATAL] Provider call failed: HTTP 401: Unauthorized
120
+ PROBLEM: Authentication failed. The API key is invalid or missing.
121
+ HINTS:
122
+ - Ensure your API key is correctly configured in the adapter or environment variables.
123
+ - Check if the API key has expired or been revoked.
124
+ ```
125
+
126
+ ## 🔌 Provider Adapters
127
+
128
+ `lemura` interacts with LLMs exclusively through the `IProviderAdapter` interface, ensuring zero lock-in.
129
+
130
+ | Adapter | Status | Description |
131
+ |---|---|---|
132
+ | `OpenAICompatibleAdapter` | ✅ Built-in | Wrapper for OpenAI and API-compatible endpoints. |
133
+
134
+ > [!TIP]
135
+ > To write a custom adapter for another provider, see the [Custom Adapter Recipe](./docs/recipes/custom-adapter.md).
136
+
137
+ ## 🤝 Contributing
138
+
139
+ We welcome contributions! Please read our [Internal Rules](./.cursor/rules/Project.md) and [Documentation Guidelines](./.cursor/rules/Documentation.md) before submitting a PR.
140
+
141
+ ## 📄 License
142
+
143
+ Distributed under the **MIT License**. See `LICENSE` for more information.
@@ -0,0 +1,45 @@
1
+ import { e as IProviderAdapter, c as CompletionRequest, d as CompletionResponse, b as CompletionChunk, M as ModelInfo, k as TranscriptionRequest, l as TranscriptionResponse, S as SynthesisRequest, A as AudioChunk, V as VisionRequest, m as VisionResponse, f as ImageGenRequest, g as ImageGenResponse } from '../adapters-BnG0LEYD.mjs';
2
+ import '../storage-DMcliVVj.mjs';
3
+ import '../rag-La_Bo-J8.mjs';
4
+ import '../logger-DxvKliuk.mjs';
5
+
6
+ interface RetryConfig {
7
+ maxRetries: number;
8
+ baseDelayMs: number;
9
+ }
10
+ interface OpenAICompatibleAdapterConfig {
11
+ baseUrl?: string;
12
+ apiKey?: string;
13
+ defaultModel?: string;
14
+ defaultHeaders?: Record<string, string>;
15
+ timeout?: number;
16
+ retry?: RetryConfig;
17
+ }
18
+ /**
19
+ * Reference implementation of an OpenAI-compatible provider adapter.
20
+ */
21
+ declare class OpenAICompatibleAdapter implements IProviderAdapter {
22
+ readonly name = "openai_compatible";
23
+ readonly version = "1.0.0";
24
+ private baseUrl;
25
+ private apiKey;
26
+ private defaultModel;
27
+ private defaultHeaders;
28
+ private timeoutMs;
29
+ private retryConfig;
30
+ constructor(config?: OpenAICompatibleAdapterConfig);
31
+ private fetchWithRetry;
32
+ private mapFinishReason;
33
+ private buildPayload;
34
+ complete(request: CompletionRequest): Promise<CompletionResponse>;
35
+ stream(request: CompletionRequest): AsyncIterable<CompletionChunk>;
36
+ estimateTokens(text: string): number;
37
+ getModelInfo(): ModelInfo;
38
+ healthCheck(): Promise<boolean>;
39
+ transcribe(request: TranscriptionRequest): Promise<TranscriptionResponse>;
40
+ synthesize(request: SynthesisRequest): AsyncIterable<AudioChunk>;
41
+ describeImage(request: VisionRequest): Promise<VisionResponse>;
42
+ generateImage(request: ImageGenRequest): Promise<ImageGenResponse>;
43
+ }
44
+
45
+ export { OpenAICompatibleAdapter, type OpenAICompatibleAdapterConfig, type RetryConfig };
@@ -0,0 +1,45 @@
1
+ import { e as IProviderAdapter, c as CompletionRequest, d as CompletionResponse, b as CompletionChunk, M as ModelInfo, k as TranscriptionRequest, l as TranscriptionResponse, S as SynthesisRequest, A as AudioChunk, V as VisionRequest, m as VisionResponse, f as ImageGenRequest, g as ImageGenResponse } from '../adapters-BSkhv5ac.js';
2
+ import '../storage-BGu4Loao.js';
3
+ import '../rag-La_Bo-J8.js';
4
+ import '../logger-DxvKliuk.js';
5
+
6
+ interface RetryConfig {
7
+ maxRetries: number;
8
+ baseDelayMs: number;
9
+ }
10
+ interface OpenAICompatibleAdapterConfig {
11
+ baseUrl?: string;
12
+ apiKey?: string;
13
+ defaultModel?: string;
14
+ defaultHeaders?: Record<string, string>;
15
+ timeout?: number;
16
+ retry?: RetryConfig;
17
+ }
18
+ /**
19
+ * Reference implementation of an OpenAI-compatible provider adapter.
20
+ */
21
+ declare class OpenAICompatibleAdapter implements IProviderAdapter {
22
+ readonly name = "openai_compatible";
23
+ readonly version = "1.0.0";
24
+ private baseUrl;
25
+ private apiKey;
26
+ private defaultModel;
27
+ private defaultHeaders;
28
+ private timeoutMs;
29
+ private retryConfig;
30
+ constructor(config?: OpenAICompatibleAdapterConfig);
31
+ private fetchWithRetry;
32
+ private mapFinishReason;
33
+ private buildPayload;
34
+ complete(request: CompletionRequest): Promise<CompletionResponse>;
35
+ stream(request: CompletionRequest): AsyncIterable<CompletionChunk>;
36
+ estimateTokens(text: string): number;
37
+ getModelInfo(): ModelInfo;
38
+ healthCheck(): Promise<boolean>;
39
+ transcribe(request: TranscriptionRequest): Promise<TranscriptionResponse>;
40
+ synthesize(request: SynthesisRequest): AsyncIterable<AudioChunk>;
41
+ describeImage(request: VisionRequest): Promise<VisionResponse>;
42
+ generateImage(request: ImageGenRequest): Promise<ImageGenResponse>;
43
+ }
44
+
45
+ export { OpenAICompatibleAdapter, type OpenAICompatibleAdapterConfig, type RetryConfig };
@@ -0,0 +1,371 @@
1
+ 'use strict';
2
+
3
+ // src/types/errors.ts
4
+ var LemuraError = class extends Error {
5
+ /**
6
+ * @param message - The error message
7
+ * @param code - The error code for programmatic handling
8
+ * @param problem - A clear description of the problem for the end user
9
+ * @param hints - A list of suggestions to resolve the issue
10
+ */
11
+ constructor(message, code, problem, hints = []) {
12
+ super(message);
13
+ this.code = code;
14
+ this.problem = problem;
15
+ this.hints = hints;
16
+ this.name = "LemuraError";
17
+ Object.setPrototypeOf(this, new.target.prototype);
18
+ }
19
+ };
20
+ var LemuraAdapterError = class extends LemuraError {
21
+ constructor(message, code = "ADAPTER_ERROR", cause, problem, hints = []) {
22
+ super(message, code, problem, hints);
23
+ this.cause = cause;
24
+ this.name = "LemuraAdapterError";
25
+ }
26
+ };
27
+
28
+ // src/adapters/OpenAICompatibleAdapter.ts
29
+ var OpenAICompatibleAdapter = class {
30
+ name = "openai_compatible";
31
+ version = "1.0.0";
32
+ baseUrl;
33
+ apiKey;
34
+ defaultModel;
35
+ defaultHeaders;
36
+ timeoutMs;
37
+ retryConfig;
38
+ constructor(config = {}) {
39
+ this.baseUrl = (config.baseUrl ?? process.env.LEMURA_BASE_URL ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/$/, "");
40
+ this.apiKey = config.apiKey ?? process.env.LEMURA_API_KEY ?? process.env.OPENAI_API_KEY ?? "";
41
+ this.defaultModel = config.defaultModel ?? process.env.LEMURA_MODEL ?? process.env.OPENAI_MODEL ?? "gpt-3.5-turbo";
42
+ this.defaultHeaders = config.defaultHeaders || {};
43
+ this.timeoutMs = config.timeout || 3e4;
44
+ this.retryConfig = config.retry || { maxRetries: 2, baseDelayMs: 1e3 };
45
+ }
46
+ async fetchWithRetry(url, init) {
47
+ let attempts = 0;
48
+ while (attempts <= this.retryConfig.maxRetries) {
49
+ try {
50
+ const controller = new AbortController();
51
+ const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
52
+ const headers = {
53
+ "Authorization": `Bearer ${this.apiKey}`,
54
+ ...this.defaultHeaders
55
+ };
56
+ if (init.headers) {
57
+ Object.assign(headers, init.headers);
58
+ }
59
+ if (headers["Content-Type"] === "unset") {
60
+ delete headers["Content-Type"];
61
+ } else if (!headers["Content-Type"]) {
62
+ headers["Content-Type"] = "application/json";
63
+ }
64
+ const response = await fetch(url, {
65
+ ...init,
66
+ signal: controller.signal,
67
+ headers
68
+ });
69
+ clearTimeout(timeoutId);
70
+ if (response.ok) return response;
71
+ if ((response.status === 429 || response.status === 503) && attempts < this.retryConfig.maxRetries) {
72
+ attempts++;
73
+ const delay = this.retryConfig.baseDelayMs * Math.pow(2, attempts - 1);
74
+ await new Promise((resolve) => setTimeout(resolve, delay));
75
+ continue;
76
+ }
77
+ const errorText = await response.text().catch(() => "");
78
+ let problem = "The server replied with an error during the API call.";
79
+ let hints = ["Check the API documentation for the provider you are using."];
80
+ if (response.status === 401) {
81
+ problem = "Authentication failed. The API key is invalid or missing.";
82
+ hints = [
83
+ "Ensure your API key is correctly configured in the adapter or environment variables.",
84
+ "Check if the API key has expired or been revoked."
85
+ ];
86
+ } else if (response.status === 404) {
87
+ problem = "The requested resource or model was not found.";
88
+ hints = [
89
+ "Verify that the baseUrl is correct (e.g., https://api.openai.com/v1).",
90
+ "Check if the model name is correct and available for your account.",
91
+ "Ensure you are not appending extra paths to the baseUrl."
92
+ ];
93
+ } else if (response.status === 429) {
94
+ problem = "Rate limit exceeded.";
95
+ hints = [
96
+ "Wait a few seconds before retrying.",
97
+ "Check your usage limits and billing status on the provider dashboard."
98
+ ];
99
+ }
100
+ throw new LemuraAdapterError(
101
+ `HTTP ${response.status}: ${errorText}`,
102
+ "HTTP_ERROR",
103
+ { status: response.status, body: errorText },
104
+ problem,
105
+ hints
106
+ );
107
+ } catch (err) {
108
+ if (err instanceof LemuraAdapterError) throw err;
109
+ if (attempts < this.retryConfig.maxRetries) {
110
+ attempts++;
111
+ const delay = this.retryConfig.baseDelayMs * Math.pow(2, attempts - 1);
112
+ await new Promise((resolve) => setTimeout(resolve, delay));
113
+ continue;
114
+ }
115
+ throw new LemuraAdapterError(
116
+ `Network request failed: ${err instanceof Error ? err.message : String(err)}`,
117
+ "NETWORK_ERROR",
118
+ err,
119
+ "A network error occurred while connecting to the provider.",
120
+ [
121
+ "Check your internet connection.",
122
+ "Verify that the baseUrl is reachable from your network.",
123
+ "Check for proxy or firewall settings that might block the request."
124
+ ]
125
+ );
126
+ }
127
+ }
128
+ throw new LemuraAdapterError(
129
+ "Max retries exceeded",
130
+ "MAX_RETRIES",
131
+ void 0,
132
+ "The request failed after multiple retry attempts.",
133
+ ["Check if the provider service is down or experiencing high load."]
134
+ );
135
+ }
136
+ mapFinishReason(reason) {
137
+ if (!reason) return "stop";
138
+ const r = reason.toLowerCase();
139
+ if (r === "tool_calls" || r === "tool_call") return "tool_call";
140
+ if (r === "length" || r === "max_tokens") return "max_tokens";
141
+ if (r === "content_filter" || r === "error") return "error";
142
+ return "stop";
143
+ }
144
+ buildPayload(request) {
145
+ const payload = {
146
+ model: request.model || this.defaultModel,
147
+ messages: request.messages
148
+ };
149
+ if (request.maxTokens !== void 0) payload.max_tokens = request.maxTokens;
150
+ if (request.temperature !== void 0) payload.temperature = request.temperature;
151
+ if (request.stopSequences?.length) payload.stop = request.stopSequences;
152
+ if (request.stream) payload.stream = true;
153
+ if (request.tools && request.tools.length > 0) {
154
+ payload.tools = request.tools.map((t) => ({
155
+ type: "function",
156
+ function: {
157
+ name: t.name,
158
+ description: t.description,
159
+ parameters: t.parameters
160
+ }
161
+ }));
162
+ }
163
+ return payload;
164
+ }
165
+ async complete(request) {
166
+ const payload = this.buildPayload(request);
167
+ const response = await this.fetchWithRetry(`${this.baseUrl}/chat/completions`, {
168
+ method: "POST",
169
+ body: JSON.stringify(payload)
170
+ });
171
+ const data = await response.json();
172
+ const choice = data.choices?.[0];
173
+ if (!choice) {
174
+ throw new LemuraAdapterError("Invalid response format: missing choices", "INVALID_RESPONSE", data);
175
+ }
176
+ const message = choice.message;
177
+ let toolCalls;
178
+ if (message.tool_calls && message.tool_calls.length > 0) {
179
+ toolCalls = message.tool_calls.map((tc) => ({
180
+ id: tc.id,
181
+ name: tc.function.name,
182
+ arguments: tc.function.arguments
183
+ }));
184
+ }
185
+ return {
186
+ content: message.content || "",
187
+ toolCalls,
188
+ finishReason: this.mapFinishReason(choice.finish_reason),
189
+ usage: {
190
+ promptTokens: data.usage?.prompt_tokens || 0,
191
+ completionTokens: data.usage?.completion_tokens || 0,
192
+ totalTokens: data.usage?.total_tokens || 0
193
+ },
194
+ rawResponse: data
195
+ };
196
+ }
197
+ async *stream(request) {
198
+ const payload = this.buildPayload({ ...request, stream: true });
199
+ const response = await this.fetchWithRetry(`${this.baseUrl}/chat/completions`, {
200
+ method: "POST",
201
+ body: JSON.stringify(payload)
202
+ });
203
+ if (!response.body) {
204
+ throw new LemuraAdapterError("Response body is null", "STREAM_ERROR");
205
+ }
206
+ const reader = response.body.getReader();
207
+ const decoder = new TextDecoder("utf-8");
208
+ let buffer = "";
209
+ try {
210
+ while (true) {
211
+ const { value, done } = await reader.read();
212
+ if (done) break;
213
+ buffer += decoder.decode(value, { stream: true });
214
+ const lines = buffer.split("\n");
215
+ buffer = lines.pop() || "";
216
+ for (const line of lines) {
217
+ const trimmed = line.trim();
218
+ if (!trimmed || trimmed === "data: [DONE]") continue;
219
+ if (trimmed.startsWith("data: ")) {
220
+ const jsonStr = trimmed.slice(6);
221
+ let data;
222
+ try {
223
+ data = JSON.parse(jsonStr);
224
+ } catch (err) {
225
+ continue;
226
+ }
227
+ const choice = data.choices?.[0];
228
+ if (!choice) continue;
229
+ const delta = choice.delta?.content || "";
230
+ const toolCallBlock = choice.delta?.tool_calls?.[0];
231
+ let toolCallDelta;
232
+ if (toolCallBlock) {
233
+ toolCallDelta = {
234
+ id: toolCallBlock.id,
235
+ name: toolCallBlock.function?.name,
236
+ arguments: toolCallBlock.function?.arguments
237
+ };
238
+ }
239
+ const isFinished = choice.finish_reason !== null && choice.finish_reason !== void 0;
240
+ yield {
241
+ delta,
242
+ finished: isFinished,
243
+ ...toolCallDelta && { toolCallDelta },
244
+ ...isFinished && { finishReason: this.mapFinishReason(choice.finish_reason) }
245
+ };
246
+ }
247
+ }
248
+ }
249
+ } finally {
250
+ reader.releaseLock();
251
+ }
252
+ }
253
+ estimateTokens(text) {
254
+ return Math.ceil(text.length / 4);
255
+ }
256
+ getModelInfo() {
257
+ return {
258
+ supportsVision: true,
259
+ supportsTools: true,
260
+ contextWindow: 128e3
261
+ };
262
+ }
263
+ async healthCheck() {
264
+ try {
265
+ const resp = await this.fetchWithRetry(`${this.baseUrl}/models`, { method: "GET" });
266
+ return resp.ok;
267
+ } catch {
268
+ return false;
269
+ }
270
+ }
271
+ async transcribe(request) {
272
+ const binaryString = atob(request.audioBase64);
273
+ const bytes = new Uint8Array(binaryString.length);
274
+ for (let i = 0; i < binaryString.length; i++) {
275
+ bytes[i] = binaryString.charCodeAt(i);
276
+ }
277
+ const blob = new Blob([bytes], { type: request.mimeType });
278
+ const formData = new FormData();
279
+ formData.append("file", blob, "audio.webm");
280
+ formData.append("model", "whisper-1");
281
+ if (request.language) formData.append("language", request.language);
282
+ const response = await this.fetchWithRetry(`${this.baseUrl}/audio/transcriptions`, {
283
+ method: "POST",
284
+ body: formData,
285
+ headers: {
286
+ "Content-Type": "unset"
287
+ }
288
+ });
289
+ const data = await response.json();
290
+ return {
291
+ transcript: data.text,
292
+ confidence: 1,
293
+ // OpenAI doesn't return confidence in standard response
294
+ language: data.language || request.language || "en"
295
+ };
296
+ }
297
+ async *synthesize(request) {
298
+ const response = await this.fetchWithRetry(`${this.baseUrl}/audio/speech`, {
299
+ method: "POST",
300
+ body: JSON.stringify({
301
+ model: "tts-1",
302
+ input: request.text,
303
+ voice: request.voiceId || "alloy",
304
+ response_format: request.format || "mp3"
305
+ })
306
+ });
307
+ if (!response.body) throw new LemuraAdapterError("No response body for TTS", "STREAM_ERROR");
308
+ const reader = response.body.getReader();
309
+ try {
310
+ while (true) {
311
+ const { done, value } = await reader.read();
312
+ if (done) break;
313
+ if (value) {
314
+ const binary = new TextDecoder("latin1").decode(value);
315
+ yield { audioBase64: btoa(binary) };
316
+ }
317
+ }
318
+ } finally {
319
+ reader.releaseLock();
320
+ }
321
+ }
322
+ async describeImage(request) {
323
+ const payload = {
324
+ model: this.defaultModel,
325
+ messages: [
326
+ {
327
+ role: "user",
328
+ content: [
329
+ { type: "text", text: request.prompt || "Describe this image" },
330
+ {
331
+ type: "image_url",
332
+ image_url: {
333
+ url: `data:image/jpeg;base64,${request.imageBase64}`
334
+ }
335
+ }
336
+ ]
337
+ }
338
+ ]
339
+ };
340
+ const response = await this.fetchWithRetry(`${this.baseUrl}/chat/completions`, {
341
+ method: "POST",
342
+ body: JSON.stringify(payload)
343
+ });
344
+ const data = await response.json();
345
+ return {
346
+ description: data.choices[0].message.content,
347
+ objects: []
348
+ // OpenAI doesn't return structured objects in standard vision call
349
+ };
350
+ }
351
+ async generateImage(request) {
352
+ const response = await this.fetchWithRetry(`${this.baseUrl}/images/generations`, {
353
+ method: "POST",
354
+ body: JSON.stringify({
355
+ prompt: request.prompt,
356
+ model: "dall-e-3",
357
+ n: 1,
358
+ size: request.dimensions || "1024x1024"
359
+ })
360
+ });
361
+ const data = await response.json();
362
+ return {
363
+ imageUrl: data.data[0].url,
364
+ revisedPrompt: data.data[0].revised_prompt
365
+ };
366
+ }
367
+ };
368
+
369
+ exports.OpenAICompatibleAdapter = OpenAICompatibleAdapter;
370
+ //# sourceMappingURL=index.js.map
371
+ //# sourceMappingURL=index.js.map