@matthesketh/utopia-ai 0.8.0 → 0.8.1

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.
@@ -46,6 +46,9 @@ function anthropicAdapter(config) {
46
46
  '@matthesketh/utopia-ai: "@anthropic-ai/sdk" package is required for the Anthropic adapter. Install it with: npm install @anthropic-ai/sdk'
47
47
  );
48
48
  }
49
+ if (config.baseURL && !/^https?:$/.test(new URL(config.baseURL).protocol)) {
50
+ throw new Error(`Anthropic baseURL must be http(s): ${config.baseURL}`);
51
+ }
49
52
  client = new AnthropicCtor({
50
53
  apiKey: config.apiKey,
51
54
  ...config.baseURL ? { baseURL: config.baseURL } : {}
@@ -1,4 +1,4 @@
1
- import { b as AnthropicConfig, A as AIAdapter } from '../types-FSnS43LM.cjs';
1
+ import { b as AnthropicConfig, A as AIAdapter } from '../types-BcCKlL06.cjs';
2
2
 
3
3
  /**
4
4
  * Create an Anthropic adapter.
@@ -1,4 +1,4 @@
1
- import { b as AnthropicConfig, A as AIAdapter } from '../types-FSnS43LM.js';
1
+ import { b as AnthropicConfig, A as AIAdapter } from '../types-BcCKlL06.js';
2
2
 
3
3
  /**
4
4
  * Create an Anthropic adapter.
@@ -12,6 +12,9 @@ function anthropicAdapter(config) {
12
12
  '@matthesketh/utopia-ai: "@anthropic-ai/sdk" package is required for the Anthropic adapter. Install it with: npm install @anthropic-ai/sdk'
13
13
  );
14
14
  }
15
+ if (config.baseURL && !/^https?:$/.test(new URL(config.baseURL).protocol)) {
16
+ throw new Error(`Anthropic baseURL must be http(s): ${config.baseURL}`);
17
+ }
15
18
  client = new AnthropicCtor({
16
19
  apiKey: config.apiKey,
17
20
  ...config.baseURL ? { baseURL: config.baseURL } : {}
@@ -1,4 +1,4 @@
1
- import { G as GoogleConfig, A as AIAdapter } from '../types-FSnS43LM.cjs';
1
+ import { G as GoogleConfig, A as AIAdapter } from '../types-BcCKlL06.cjs';
2
2
 
3
3
  /**
4
4
  * Create a Google Gemini adapter.
@@ -1,4 +1,4 @@
1
- import { G as GoogleConfig, A as AIAdapter } from '../types-FSnS43LM.js';
1
+ import { G as GoogleConfig, A as AIAdapter } from '../types-BcCKlL06.js';
2
2
 
3
3
  /**
4
4
  * Create a Google Gemini adapter.
@@ -25,9 +25,18 @@ __export(ollama_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(ollama_exports);
27
27
  var TRAILING_SLASH_RE = /\/$/;
28
+ var MAX_STREAM_BUFFER = 1024 * 1024;
29
+ function resolveBaseURL(raw) {
30
+ const baseURL = (raw ?? "http://localhost:11434").replace(TRAILING_SLASH_RE, "");
31
+ const protocol = new URL(baseURL).protocol;
32
+ if (protocol !== "http:" && protocol !== "https:") {
33
+ throw new Error(`Ollama baseURL must be http(s): ${baseURL}`);
34
+ }
35
+ return baseURL;
36
+ }
28
37
  var ollamaToolCallCounter = 0;
29
38
  function ollamaAdapter(config = {}) {
30
- const baseURL = (config.baseURL ?? "http://localhost:11434").replace(TRAILING_SLASH_RE, "");
39
+ const baseURL = resolveBaseURL(config.baseURL);
31
40
  return {
32
41
  async chat(request) {
33
42
  const model = request.model ?? config.defaultModel ?? "llama3.2";
@@ -109,6 +118,9 @@ function ollamaAdapter(config = {}) {
109
118
  const { done, value } = await reader.read();
110
119
  if (done) break;
111
120
  buffer += decoder.decode(value, { stream: true });
121
+ if (buffer.length > MAX_STREAM_BUFFER) {
122
+ throw new Error("Ollama stream exceeded maximum buffer size without a line delimiter");
123
+ }
112
124
  const lines = buffer.split("\n");
113
125
  buffer = lines.pop() ?? "";
114
126
  for (const line of lines) {
@@ -1,6 +1,6 @@
1
- import { O as OllamaConfig, A as AIAdapter } from '../types-FSnS43LM.cjs';
1
+ import { O as OllamaConfig, A as AIAdapter } from '../types-BcCKlL06.cjs';
2
2
 
3
- /** Matches a trailing slash for URL normalization. */
3
+ /** matches a trailing slash for URL normalization. */
4
4
  declare const TRAILING_SLASH_RE: RegExp;
5
5
  /**
6
6
  * Create an Ollama adapter for local models.
@@ -1,6 +1,6 @@
1
- import { O as OllamaConfig, A as AIAdapter } from '../types-FSnS43LM.js';
1
+ import { O as OllamaConfig, A as AIAdapter } from '../types-BcCKlL06.js';
2
2
 
3
- /** Matches a trailing slash for URL normalization. */
3
+ /** matches a trailing slash for URL normalization. */
4
4
  declare const TRAILING_SLASH_RE: RegExp;
5
5
  /**
6
6
  * Create an Ollama adapter for local models.
@@ -1,8 +1,17 @@
1
1
  // src/adapters/ollama.ts
2
2
  var TRAILING_SLASH_RE = /\/$/;
3
+ var MAX_STREAM_BUFFER = 1024 * 1024;
4
+ function resolveBaseURL(raw) {
5
+ const baseURL = (raw ?? "http://localhost:11434").replace(TRAILING_SLASH_RE, "");
6
+ const protocol = new URL(baseURL).protocol;
7
+ if (protocol !== "http:" && protocol !== "https:") {
8
+ throw new Error(`Ollama baseURL must be http(s): ${baseURL}`);
9
+ }
10
+ return baseURL;
11
+ }
3
12
  var ollamaToolCallCounter = 0;
4
13
  function ollamaAdapter(config = {}) {
5
- const baseURL = (config.baseURL ?? "http://localhost:11434").replace(TRAILING_SLASH_RE, "");
14
+ const baseURL = resolveBaseURL(config.baseURL);
6
15
  return {
7
16
  async chat(request) {
8
17
  const model = request.model ?? config.defaultModel ?? "llama3.2";
@@ -84,6 +93,9 @@ function ollamaAdapter(config = {}) {
84
93
  const { done, value } = await reader.read();
85
94
  if (done) break;
86
95
  buffer += decoder.decode(value, { stream: true });
96
+ if (buffer.length > MAX_STREAM_BUFFER) {
97
+ throw new Error("Ollama stream exceeded maximum buffer size without a line delimiter");
98
+ }
87
99
  const lines = buffer.split("\n");
88
100
  buffer = lines.pop() ?? "";
89
101
  for (const line of lines) {
@@ -46,6 +46,9 @@ function openaiAdapter(config) {
46
46
  '@matthesketh/utopia-ai: "openai" package is required for the OpenAI adapter. Install it with: npm install openai'
47
47
  );
48
48
  }
49
+ if (config.baseURL && !/^https?:$/.test(new URL(config.baseURL).protocol)) {
50
+ throw new Error(`OpenAI baseURL must be http(s): ${config.baseURL}`);
51
+ }
49
52
  client = new OpenAICtor({
50
53
  apiKey: config.apiKey,
51
54
  baseURL: config.baseURL,
@@ -1,4 +1,4 @@
1
- import { h as OpenAIConfig, A as AIAdapter } from '../types-FSnS43LM.cjs';
1
+ import { h as OpenAIConfig, A as AIAdapter } from '../types-BcCKlL06.cjs';
2
2
 
3
3
  /**
4
4
  * Create an OpenAI adapter.
@@ -1,4 +1,4 @@
1
- import { h as OpenAIConfig, A as AIAdapter } from '../types-FSnS43LM.js';
1
+ import { h as OpenAIConfig, A as AIAdapter } from '../types-BcCKlL06.js';
2
2
 
3
3
  /**
4
4
  * Create an OpenAI adapter.
@@ -12,6 +12,9 @@ function openaiAdapter(config) {
12
12
  '@matthesketh/utopia-ai: "openai" package is required for the OpenAI adapter. Install it with: npm install openai'
13
13
  );
14
14
  }
15
+ if (config.baseURL && !/^https?:$/.test(new URL(config.baseURL).protocol)) {
16
+ throw new Error(`OpenAI baseURL must be http(s): ${config.baseURL}`);
17
+ }
15
18
  client = new OpenAICtor({
16
19
  apiKey: config.apiKey,
17
20
  baseURL: config.baseURL,
@@ -0,0 +1,66 @@
1
+ import { d as ChatRequest, e as ChatResponse, C as ChatChunk, E as EmbeddingRequest, f as EmbeddingResponse, c as ChatMessage, l as ToolDefinition, j as ToolCall, a as AIHooks, R as RetryConfig, A as AIAdapter } from './types-BcCKlL06.js';
2
+
3
+ interface AI {
4
+ /** Send a chat completion request. */
5
+ chat(request: ChatRequest): Promise<ChatResponse>;
6
+ /** Stream a chat completion. */
7
+ stream(request: ChatRequest): AsyncIterable<ChatChunk>;
8
+ /** Generate embeddings. */
9
+ embeddings(request: EmbeddingRequest): Promise<EmbeddingResponse>;
10
+ /**
11
+ * Run a tool-calling loop: send messages, execute tool calls via the
12
+ * provided handlers, append results, and repeat until the model stops
13
+ * calling tools.
14
+ */
15
+ run(options: RunOptions): Promise<ChatResponse>;
16
+ }
17
+ interface ToolHandler {
18
+ definition: ToolDefinition;
19
+ handler: (args: Record<string, unknown>) => Promise<unknown> | unknown;
20
+ }
21
+ interface RunOptions {
22
+ messages: ChatMessage[];
23
+ tools: ToolHandler[];
24
+ model?: string;
25
+ temperature?: number;
26
+ maxTokens?: number;
27
+ maxRounds?: number;
28
+ onToolCall?: (call: ToolCall, result: unknown) => void;
29
+ extra?: Record<string, unknown>;
30
+ }
31
+ interface CreateAIOptions {
32
+ hooks?: AIHooks;
33
+ retry?: RetryConfig;
34
+ }
35
+ /**
36
+ * Create an AI instance with the given adapter.
37
+ *
38
+ * Usage:
39
+ * ```ts
40
+ * import { createAI } from '@matthesketh/utopia-ai';
41
+ * import { openaiAdapter } from '@matthesketh/utopia-ai/openai';
42
+ *
43
+ * const ai = createAI(openaiAdapter({ apiKey: process.env.OPENAI_API_KEY }));
44
+ *
45
+ * const res = await ai.chat({
46
+ * messages: [{ role: 'user', content: 'Hello!' }],
47
+ * });
48
+ *
49
+ * // Streaming
50
+ * for await (const chunk of ai.stream({ messages })) {
51
+ * process.stdout.write(chunk.delta);
52
+ * }
53
+ *
54
+ * // Agentic tool loop
55
+ * const result = await ai.run({
56
+ * messages: [{ role: 'user', content: 'What is the weather?' }],
57
+ * tools: [{
58
+ * definition: { name: 'get_weather', description: '...', parameters: { type: 'object', properties: {} } },
59
+ * handler: async ({ city }) => ({ temp: 72 }),
60
+ * }],
61
+ * });
62
+ * ```
63
+ */
64
+ declare function createAI(adapter: AIAdapter, options?: CreateAIOptions): AI;
65
+
66
+ export { type AI as A, type CreateAIOptions as C, type RunOptions as R, type ToolHandler as T, createAI as c };
@@ -0,0 +1,66 @@
1
+ import { d as ChatRequest, e as ChatResponse, C as ChatChunk, E as EmbeddingRequest, f as EmbeddingResponse, c as ChatMessage, l as ToolDefinition, j as ToolCall, a as AIHooks, R as RetryConfig, A as AIAdapter } from './types-BcCKlL06.cjs';
2
+
3
+ interface AI {
4
+ /** Send a chat completion request. */
5
+ chat(request: ChatRequest): Promise<ChatResponse>;
6
+ /** Stream a chat completion. */
7
+ stream(request: ChatRequest): AsyncIterable<ChatChunk>;
8
+ /** Generate embeddings. */
9
+ embeddings(request: EmbeddingRequest): Promise<EmbeddingResponse>;
10
+ /**
11
+ * Run a tool-calling loop: send messages, execute tool calls via the
12
+ * provided handlers, append results, and repeat until the model stops
13
+ * calling tools.
14
+ */
15
+ run(options: RunOptions): Promise<ChatResponse>;
16
+ }
17
+ interface ToolHandler {
18
+ definition: ToolDefinition;
19
+ handler: (args: Record<string, unknown>) => Promise<unknown> | unknown;
20
+ }
21
+ interface RunOptions {
22
+ messages: ChatMessage[];
23
+ tools: ToolHandler[];
24
+ model?: string;
25
+ temperature?: number;
26
+ maxTokens?: number;
27
+ maxRounds?: number;
28
+ onToolCall?: (call: ToolCall, result: unknown) => void;
29
+ extra?: Record<string, unknown>;
30
+ }
31
+ interface CreateAIOptions {
32
+ hooks?: AIHooks;
33
+ retry?: RetryConfig;
34
+ }
35
+ /**
36
+ * Create an AI instance with the given adapter.
37
+ *
38
+ * Usage:
39
+ * ```ts
40
+ * import { createAI } from '@matthesketh/utopia-ai';
41
+ * import { openaiAdapter } from '@matthesketh/utopia-ai/openai';
42
+ *
43
+ * const ai = createAI(openaiAdapter({ apiKey: process.env.OPENAI_API_KEY }));
44
+ *
45
+ * const res = await ai.chat({
46
+ * messages: [{ role: 'user', content: 'Hello!' }],
47
+ * });
48
+ *
49
+ * // Streaming
50
+ * for await (const chunk of ai.stream({ messages })) {
51
+ * process.stdout.write(chunk.delta);
52
+ * }
53
+ *
54
+ * // Agentic tool loop
55
+ * const result = await ai.run({
56
+ * messages: [{ role: 'user', content: 'What is the weather?' }],
57
+ * tools: [{
58
+ * definition: { name: 'get_weather', description: '...', parameters: { type: 'object', properties: {} } },
59
+ * handler: async ({ city }) => ({ temp: 72 }),
60
+ * }],
61
+ * });
62
+ * ```
63
+ */
64
+ declare function createAI(adapter: AIAdapter, options?: CreateAIOptions): AI;
65
+
66
+ export { type AI as A, type CreateAIOptions as C, type RunOptions as R, type ToolHandler as T, createAI as c };
package/dist/index.cjs CHANGED
@@ -209,6 +209,7 @@ async function* chatToStream(adapter, request) {
209
209
  }
210
210
 
211
211
  // src/streaming.ts
212
+ var MAX_SSE_BUFFER = 1024 * 1024;
212
213
  async function streamSSE(res, stream, options) {
213
214
  res.writeHead(200, {
214
215
  "Content-Type": "text/event-stream",
@@ -247,6 +248,9 @@ async function* parseSSEStream(response) {
247
248
  const { done, value } = await reader.read();
248
249
  if (done) break;
249
250
  buffer += decoder.decode(value, { stream: true });
251
+ if (buffer.length > MAX_SSE_BUFFER) {
252
+ throw new Error("SSE stream exceeded maximum buffer size without a line delimiter");
253
+ }
250
254
  const lines = buffer.split("\n");
251
255
  buffer = lines.pop() ?? "";
252
256
  for (const line of lines) {
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- export { A as AI, C as CreateAIOptions, R as RunOptions, T as ToolHandler, c as createAI } from './ai-B2NkPypc.cjs';
1
+ export { A as AI, C as CreateAIOptions, R as RunOptions, T as ToolHandler, c as createAI } from './ai-dyp9MfTz.cjs';
2
2
  import { ServerResponse } from 'node:http';
3
- import { C as ChatChunk } from './types-FSnS43LM.cjs';
4
- export { A as AIAdapter, a as AIHooks, b as AnthropicConfig, c as ChatMessage, d as ChatRequest, e as ChatResponse, E as EmbeddingRequest, f as EmbeddingResponse, G as GoogleConfig, I as ImageContent, J as JsonSchema, M as MessageContent, g as MessageRole, O as OllamaConfig, h as OpenAIConfig, R as RetryConfig, T as TextContent, i as TokenUsage, j as ToolCall, k as ToolCallContent, l as ToolDefinition, m as ToolResultContent } from './types-FSnS43LM.cjs';
3
+ import { C as ChatChunk } from './types-BcCKlL06.cjs';
4
+ export { A as AIAdapter, a as AIHooks, b as AnthropicConfig, c as ChatMessage, d as ChatRequest, e as ChatResponse, E as EmbeddingRequest, f as EmbeddingResponse, G as GoogleConfig, I as ImageContent, J as JsonSchema, M as MessageContent, g as MessageRole, O as OllamaConfig, h as OpenAIConfig, R as RetryConfig, T as TextContent, i as TokenUsage, j as ToolCall, k as ToolCallContent, l as ToolDefinition, m as ToolResultContent } from './types-BcCKlL06.cjs';
5
5
 
6
6
  /**
7
7
  * Stream AI chat chunks as Server-Sent Events (SSE).
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { A as AI, C as CreateAIOptions, R as RunOptions, T as ToolHandler, c as createAI } from './ai-VMRaOOak.js';
1
+ export { A as AI, C as CreateAIOptions, R as RunOptions, T as ToolHandler, c as createAI } from './ai-B65XFWfZ.js';
2
2
  import { ServerResponse } from 'node:http';
3
- import { C as ChatChunk } from './types-FSnS43LM.js';
4
- export { A as AIAdapter, a as AIHooks, b as AnthropicConfig, c as ChatMessage, d as ChatRequest, e as ChatResponse, E as EmbeddingRequest, f as EmbeddingResponse, G as GoogleConfig, I as ImageContent, J as JsonSchema, M as MessageContent, g as MessageRole, O as OllamaConfig, h as OpenAIConfig, R as RetryConfig, T as TextContent, i as TokenUsage, j as ToolCall, k as ToolCallContent, l as ToolDefinition, m as ToolResultContent } from './types-FSnS43LM.js';
3
+ import { C as ChatChunk } from './types-BcCKlL06.js';
4
+ export { A as AIAdapter, a as AIHooks, b as AnthropicConfig, c as ChatMessage, d as ChatRequest, e as ChatResponse, E as EmbeddingRequest, f as EmbeddingResponse, G as GoogleConfig, I as ImageContent, J as JsonSchema, M as MessageContent, g as MessageRole, O as OllamaConfig, h as OpenAIConfig, R as RetryConfig, T as TextContent, i as TokenUsage, j as ToolCall, k as ToolCallContent, l as ToolDefinition, m as ToolResultContent } from './types-BcCKlL06.js';
5
5
 
6
6
  /**
7
7
  * Stream AI chat chunks as Server-Sent Events (SSE).
package/dist/index.js CHANGED
@@ -180,6 +180,7 @@ async function* chatToStream(adapter, request) {
180
180
  }
181
181
 
182
182
  // src/streaming.ts
183
+ var MAX_SSE_BUFFER = 1024 * 1024;
183
184
  async function streamSSE(res, stream, options) {
184
185
  res.writeHead(200, {
185
186
  "Content-Type": "text/event-stream",
@@ -218,6 +219,9 @@ async function* parseSSEStream(response) {
218
219
  const { done, value } = await reader.read();
219
220
  if (done) break;
220
221
  buffer += decoder.decode(value, { stream: true });
222
+ if (buffer.length > MAX_SSE_BUFFER) {
223
+ throw new Error("SSE stream exceeded maximum buffer size without a line delimiter");
224
+ }
221
225
  const lines = buffer.split("\n");
222
226
  buffer = lines.pop() ?? "";
223
227
  for (const line of lines) {
@@ -1,5 +1,5 @@
1
- import { J as JsonSchema } from '../types-FSnS43LM.cjs';
2
- import { T as ToolHandler } from '../ai-B2NkPypc.cjs';
1
+ import { J as JsonSchema } from '../types-BcCKlL06.cjs';
2
+ import { T as ToolHandler } from '../ai-dyp9MfTz.cjs';
3
3
  import { IncomingMessage, ServerResponse } from 'node:http';
4
4
 
5
5
  interface JsonRpcRequest {
@@ -1,5 +1,5 @@
1
- import { J as JsonSchema } from '../types-FSnS43LM.js';
2
- import { T as ToolHandler } from '../ai-VMRaOOak.js';
1
+ import { J as JsonSchema } from '../types-BcCKlL06.js';
2
+ import { T as ToolHandler } from '../ai-B65XFWfZ.js';
3
3
  import { IncomingMessage, ServerResponse } from 'node:http';
4
4
 
5
5
  interface JsonRpcRequest {
@@ -0,0 +1,157 @@
1
+ type MessageRole = 'system' | 'user' | 'assistant' | 'tool';
2
+ interface TextContent {
3
+ type: 'text';
4
+ text: string;
5
+ }
6
+ interface ImageContent {
7
+ type: 'image';
8
+ /** Base64-encoded image data or a URL. */
9
+ source: string;
10
+ mediaType?: string;
11
+ }
12
+ interface ToolCallContent {
13
+ type: 'tool_call';
14
+ id: string;
15
+ name: string;
16
+ arguments: Record<string, unknown>;
17
+ }
18
+ interface ToolResultContent {
19
+ type: 'tool_result';
20
+ id: string;
21
+ content: string;
22
+ isError?: boolean;
23
+ }
24
+ type MessageContent = string | TextContent | ImageContent | ToolCallContent | ToolResultContent;
25
+ interface ChatMessage {
26
+ role: MessageRole;
27
+ content: MessageContent | MessageContent[];
28
+ name?: string;
29
+ }
30
+ interface ToolDefinition {
31
+ name: string;
32
+ description: string;
33
+ parameters: JsonSchema;
34
+ }
35
+ interface JsonSchema {
36
+ type: string;
37
+ properties?: Record<string, JsonSchema & {
38
+ description?: string;
39
+ }>;
40
+ required?: string[];
41
+ items?: JsonSchema;
42
+ enum?: unknown[];
43
+ description?: string;
44
+ [key: string]: unknown;
45
+ }
46
+ interface ChatRequest {
47
+ messages: ChatMessage[];
48
+ model?: string;
49
+ temperature?: number;
50
+ maxTokens?: number;
51
+ topP?: number;
52
+ stop?: string[];
53
+ tools?: ToolDefinition[];
54
+ toolChoice?: 'auto' | 'none' | 'required' | {
55
+ name: string;
56
+ };
57
+ /** Adapter-specific options passed through untouched. */
58
+ extra?: Record<string, unknown>;
59
+ }
60
+ interface ToolCall {
61
+ id: string;
62
+ name: string;
63
+ arguments: Record<string, unknown>;
64
+ }
65
+ interface ChatResponse {
66
+ content: string;
67
+ toolCalls?: ToolCall[];
68
+ finishReason: 'stop' | 'tool_calls' | 'length' | 'error';
69
+ usage?: TokenUsage;
70
+ /** Raw response from the provider for advanced use cases. */
71
+ raw?: unknown;
72
+ }
73
+ interface TokenUsage {
74
+ promptTokens: number;
75
+ completionTokens: number;
76
+ totalTokens: number;
77
+ }
78
+ interface ChatChunk {
79
+ /** Incremental text delta. */
80
+ delta: string;
81
+ /** Incremental tool call delta (partial). */
82
+ toolCallDelta?: Partial<ToolCall> & {
83
+ index?: number;
84
+ };
85
+ /** Set on the final chunk. */
86
+ finishReason?: ChatResponse['finishReason'];
87
+ /** Set on the final chunk. */
88
+ usage?: TokenUsage;
89
+ }
90
+ interface EmbeddingRequest {
91
+ input: string | string[];
92
+ model?: string;
93
+ /** Adapter-specific options. */
94
+ extra?: Record<string, unknown>;
95
+ }
96
+ interface EmbeddingResponse {
97
+ embeddings: number[][];
98
+ usage?: {
99
+ totalTokens: number;
100
+ };
101
+ raw?: unknown;
102
+ }
103
+ interface AIAdapter {
104
+ /** Send a chat completion request. */
105
+ chat(request: ChatRequest): Promise<ChatResponse>;
106
+ /** Stream a chat completion. Adapters may omit this (falls back to chat). */
107
+ stream?(request: ChatRequest): AsyncIterable<ChatChunk>;
108
+ /** Generate embeddings. Optional capability. */
109
+ embeddings?(request: EmbeddingRequest): Promise<EmbeddingResponse>;
110
+ }
111
+ interface OpenAIConfig {
112
+ apiKey: string;
113
+ baseURL?: string;
114
+ organization?: string;
115
+ defaultModel?: string;
116
+ }
117
+ interface AnthropicConfig {
118
+ apiKey: string;
119
+ baseURL?: string;
120
+ defaultModel?: string;
121
+ }
122
+ interface GoogleConfig {
123
+ apiKey: string;
124
+ defaultModel?: string;
125
+ }
126
+ interface OllamaConfig {
127
+ baseURL?: string;
128
+ defaultModel?: string;
129
+ }
130
+ interface AIHooks {
131
+ /** Called before every chat request. Can modify the request. */
132
+ onBeforeChat?: (request: ChatRequest) => ChatRequest | Promise<ChatRequest>;
133
+ /** Called after every chat response. Can modify the response. */
134
+ onAfterChat?: (response: ChatResponse, request: ChatRequest) => ChatResponse | Promise<ChatResponse>;
135
+ /**
136
+ * Called on any adapter error.
137
+ *
138
+ * `context.request` carries the full prompt (and any provider error may
139
+ * originate from the underlying SDK and reference request metadata), so do
140
+ * not forward these verbatim to an untrusted or shared log sink — redact or
141
+ * summarise first.
142
+ */
143
+ onError?: (error: Error, context: {
144
+ method: string;
145
+ request?: ChatRequest;
146
+ }) => void;
147
+ }
148
+ interface RetryConfig {
149
+ /** Max number of retries (default: 0 = no retry). */
150
+ maxRetries?: number;
151
+ /** Base delay in ms (default: 1000). Doubles on each attempt (exponential backoff). */
152
+ baseDelay?: number;
153
+ /** Whether to retry on this error. Default: retries on network errors and 429/500+ status codes. */
154
+ shouldRetry?: (error: Error) => boolean;
155
+ }
156
+
157
+ export type { AIAdapter as A, ChatChunk as C, EmbeddingRequest as E, GoogleConfig as G, ImageContent as I, JsonSchema as J, MessageContent as M, OllamaConfig as O, RetryConfig as R, TextContent as T, AIHooks as a, AnthropicConfig as b, ChatMessage as c, ChatRequest as d, ChatResponse as e, EmbeddingResponse as f, MessageRole as g, OpenAIConfig as h, TokenUsage as i, ToolCall as j, ToolCallContent as k, ToolDefinition as l, ToolResultContent as m };
@@ -0,0 +1,157 @@
1
+ type MessageRole = 'system' | 'user' | 'assistant' | 'tool';
2
+ interface TextContent {
3
+ type: 'text';
4
+ text: string;
5
+ }
6
+ interface ImageContent {
7
+ type: 'image';
8
+ /** Base64-encoded image data or a URL. */
9
+ source: string;
10
+ mediaType?: string;
11
+ }
12
+ interface ToolCallContent {
13
+ type: 'tool_call';
14
+ id: string;
15
+ name: string;
16
+ arguments: Record<string, unknown>;
17
+ }
18
+ interface ToolResultContent {
19
+ type: 'tool_result';
20
+ id: string;
21
+ content: string;
22
+ isError?: boolean;
23
+ }
24
+ type MessageContent = string | TextContent | ImageContent | ToolCallContent | ToolResultContent;
25
+ interface ChatMessage {
26
+ role: MessageRole;
27
+ content: MessageContent | MessageContent[];
28
+ name?: string;
29
+ }
30
+ interface ToolDefinition {
31
+ name: string;
32
+ description: string;
33
+ parameters: JsonSchema;
34
+ }
35
+ interface JsonSchema {
36
+ type: string;
37
+ properties?: Record<string, JsonSchema & {
38
+ description?: string;
39
+ }>;
40
+ required?: string[];
41
+ items?: JsonSchema;
42
+ enum?: unknown[];
43
+ description?: string;
44
+ [key: string]: unknown;
45
+ }
46
+ interface ChatRequest {
47
+ messages: ChatMessage[];
48
+ model?: string;
49
+ temperature?: number;
50
+ maxTokens?: number;
51
+ topP?: number;
52
+ stop?: string[];
53
+ tools?: ToolDefinition[];
54
+ toolChoice?: 'auto' | 'none' | 'required' | {
55
+ name: string;
56
+ };
57
+ /** Adapter-specific options passed through untouched. */
58
+ extra?: Record<string, unknown>;
59
+ }
60
+ interface ToolCall {
61
+ id: string;
62
+ name: string;
63
+ arguments: Record<string, unknown>;
64
+ }
65
+ interface ChatResponse {
66
+ content: string;
67
+ toolCalls?: ToolCall[];
68
+ finishReason: 'stop' | 'tool_calls' | 'length' | 'error';
69
+ usage?: TokenUsage;
70
+ /** Raw response from the provider for advanced use cases. */
71
+ raw?: unknown;
72
+ }
73
+ interface TokenUsage {
74
+ promptTokens: number;
75
+ completionTokens: number;
76
+ totalTokens: number;
77
+ }
78
+ interface ChatChunk {
79
+ /** Incremental text delta. */
80
+ delta: string;
81
+ /** Incremental tool call delta (partial). */
82
+ toolCallDelta?: Partial<ToolCall> & {
83
+ index?: number;
84
+ };
85
+ /** Set on the final chunk. */
86
+ finishReason?: ChatResponse['finishReason'];
87
+ /** Set on the final chunk. */
88
+ usage?: TokenUsage;
89
+ }
90
+ interface EmbeddingRequest {
91
+ input: string | string[];
92
+ model?: string;
93
+ /** Adapter-specific options. */
94
+ extra?: Record<string, unknown>;
95
+ }
96
+ interface EmbeddingResponse {
97
+ embeddings: number[][];
98
+ usage?: {
99
+ totalTokens: number;
100
+ };
101
+ raw?: unknown;
102
+ }
103
+ interface AIAdapter {
104
+ /** Send a chat completion request. */
105
+ chat(request: ChatRequest): Promise<ChatResponse>;
106
+ /** Stream a chat completion. Adapters may omit this (falls back to chat). */
107
+ stream?(request: ChatRequest): AsyncIterable<ChatChunk>;
108
+ /** Generate embeddings. Optional capability. */
109
+ embeddings?(request: EmbeddingRequest): Promise<EmbeddingResponse>;
110
+ }
111
+ interface OpenAIConfig {
112
+ apiKey: string;
113
+ baseURL?: string;
114
+ organization?: string;
115
+ defaultModel?: string;
116
+ }
117
+ interface AnthropicConfig {
118
+ apiKey: string;
119
+ baseURL?: string;
120
+ defaultModel?: string;
121
+ }
122
+ interface GoogleConfig {
123
+ apiKey: string;
124
+ defaultModel?: string;
125
+ }
126
+ interface OllamaConfig {
127
+ baseURL?: string;
128
+ defaultModel?: string;
129
+ }
130
+ interface AIHooks {
131
+ /** Called before every chat request. Can modify the request. */
132
+ onBeforeChat?: (request: ChatRequest) => ChatRequest | Promise<ChatRequest>;
133
+ /** Called after every chat response. Can modify the response. */
134
+ onAfterChat?: (response: ChatResponse, request: ChatRequest) => ChatResponse | Promise<ChatResponse>;
135
+ /**
136
+ * Called on any adapter error.
137
+ *
138
+ * `context.request` carries the full prompt (and any provider error may
139
+ * originate from the underlying SDK and reference request metadata), so do
140
+ * not forward these verbatim to an untrusted or shared log sink — redact or
141
+ * summarise first.
142
+ */
143
+ onError?: (error: Error, context: {
144
+ method: string;
145
+ request?: ChatRequest;
146
+ }) => void;
147
+ }
148
+ interface RetryConfig {
149
+ /** Max number of retries (default: 0 = no retry). */
150
+ maxRetries?: number;
151
+ /** Base delay in ms (default: 1000). Doubles on each attempt (exponential backoff). */
152
+ baseDelay?: number;
153
+ /** Whether to retry on this error. Default: retries on network errors and 429/500+ status codes. */
154
+ shouldRetry?: (error: Error) => boolean;
155
+ }
156
+
157
+ export type { AIAdapter as A, ChatChunk as C, EmbeddingRequest as E, GoogleConfig as G, ImageContent as I, JsonSchema as J, MessageContent as M, OllamaConfig as O, RetryConfig as R, TextContent as T, AIHooks as a, AnthropicConfig as b, ChatMessage as c, ChatRequest as d, ChatResponse as e, EmbeddingResponse as f, MessageRole as g, OpenAIConfig as h, TokenUsage as i, ToolCall as j, ToolCallContent as k, ToolDefinition as l, ToolResultContent as m };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matthesketh/utopia-ai",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "AI adapters and MCP support for UtopiaJS",
5
5
  "type": "module",
6
6
  "license": "MIT",