@ank1015/providers 0.0.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.
Files changed (99) hide show
  1. package/README.md +453 -0
  2. package/biome.json +43 -0
  3. package/dist/agent/agent-loop.d.ts +5 -0
  4. package/dist/agent/agent-loop.d.ts.map +1 -0
  5. package/dist/agent/agent-loop.js +219 -0
  6. package/dist/agent/agent-loop.js.map +1 -0
  7. package/dist/agent/types.d.ts +67 -0
  8. package/dist/agent/types.d.ts.map +1 -0
  9. package/dist/agent/types.js +3 -0
  10. package/dist/agent/types.js.map +1 -0
  11. package/dist/index.d.ts +10 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +29 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/models.d.ts +3 -0
  16. package/dist/models.d.ts.map +1 -0
  17. package/dist/models.generated.d.ts +247 -0
  18. package/dist/models.generated.d.ts.map +1 -0
  19. package/dist/models.generated.js +315 -0
  20. package/dist/models.generated.js.map +1 -0
  21. package/dist/models.js +41 -0
  22. package/dist/models.js.map +1 -0
  23. package/dist/providers/convert.d.ts +6 -0
  24. package/dist/providers/convert.d.ts.map +1 -0
  25. package/dist/providers/convert.js +207 -0
  26. package/dist/providers/convert.js.map +1 -0
  27. package/dist/providers/google.d.ts +26 -0
  28. package/dist/providers/google.d.ts.map +1 -0
  29. package/dist/providers/google.js +434 -0
  30. package/dist/providers/google.js.map +1 -0
  31. package/dist/providers/openai.d.ts +17 -0
  32. package/dist/providers/openai.d.ts.map +1 -0
  33. package/dist/providers/openai.js +396 -0
  34. package/dist/providers/openai.js.map +1 -0
  35. package/dist/stream.d.ts +4 -0
  36. package/dist/stream.d.ts.map +1 -0
  37. package/dist/stream.js +40 -0
  38. package/dist/stream.js.map +1 -0
  39. package/dist/test-google-agent-loop.d.ts +2 -0
  40. package/dist/test-google-agent-loop.d.ts.map +1 -0
  41. package/dist/test-google-agent-loop.js +186 -0
  42. package/dist/test-google-agent-loop.js.map +1 -0
  43. package/dist/test-google.d.ts +2 -0
  44. package/dist/test-google.d.ts.map +1 -0
  45. package/dist/test-google.js +41 -0
  46. package/dist/test-google.js.map +1 -0
  47. package/dist/types.d.ts +187 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +10 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/utils/event-stream.d.ts +16 -0
  52. package/dist/utils/event-stream.d.ts.map +1 -0
  53. package/dist/utils/event-stream.js +61 -0
  54. package/dist/utils/event-stream.js.map +1 -0
  55. package/dist/utils/json-parse.d.ts +9 -0
  56. package/dist/utils/json-parse.d.ts.map +1 -0
  57. package/dist/utils/json-parse.js +32 -0
  58. package/dist/utils/json-parse.js.map +1 -0
  59. package/dist/utils/sanitize-unicode.d.ts +22 -0
  60. package/dist/utils/sanitize-unicode.d.ts.map +1 -0
  61. package/dist/utils/sanitize-unicode.js +29 -0
  62. package/dist/utils/sanitize-unicode.js.map +1 -0
  63. package/dist/utils/validation.d.ts +11 -0
  64. package/dist/utils/validation.d.ts.map +1 -0
  65. package/dist/utils/validation.js +61 -0
  66. package/dist/utils/validation.js.map +1 -0
  67. package/package.json +33 -0
  68. package/src/agent/agent-loop.ts +275 -0
  69. package/src/agent/types.ts +80 -0
  70. package/src/index.ts +72 -0
  71. package/src/models.generated.ts +314 -0
  72. package/src/models.ts +45 -0
  73. package/src/providers/convert.ts +222 -0
  74. package/src/providers/google.ts +496 -0
  75. package/src/providers/openai.ts +437 -0
  76. package/src/stream.ts +60 -0
  77. package/src/types.ts +198 -0
  78. package/src/utils/event-stream.ts +60 -0
  79. package/src/utils/json-parse.ts +28 -0
  80. package/src/utils/sanitize-unicode.ts +25 -0
  81. package/src/utils/validation.ts +69 -0
  82. package/test/core/agent-loop.test.ts +958 -0
  83. package/test/core/stream.test.ts +409 -0
  84. package/test/data/red-circle.png +0 -0
  85. package/test/data/superintelligentwill.pdf +0 -0
  86. package/test/edge-cases/general.test.ts +565 -0
  87. package/test/integration/e2e.test.ts +530 -0
  88. package/test/models/cost.test.ts +499 -0
  89. package/test/models/registry.test.ts +298 -0
  90. package/test/providers/convert.test.ts +846 -0
  91. package/test/providers/google-schema.test.ts +666 -0
  92. package/test/providers/google-stream.test.ts +369 -0
  93. package/test/providers/openai-stream.test.ts +251 -0
  94. package/test/utils/event-stream.test.ts +289 -0
  95. package/test/utils/json-parse.test.ts +344 -0
  96. package/test/utils/sanitize-unicode.test.ts +329 -0
  97. package/test/utils/validation.test.ts +614 -0
  98. package/tsconfig.json +21 -0
  99. package/vitest.config.ts +9 -0
@@ -0,0 +1,275 @@
1
+ import { EventStream } from "../utils/event-stream";
2
+ import { UserMessage, Api, NativeAssistantMessage, Context, AssistantMessage, ToolResultMessage } from "../types";
3
+ import { AgentContext, AgentEvent, AgentLoopConfig, AgentTool, AgentToolResult, QueuedMessage } from "./types";
4
+ import { stream as streamLLM } from "../stream";
5
+ import { validateToolArguments } from "../utils/validation";
6
+
7
+ // Main prompt function - returns a stream of events
8
+ export function agentLoop<TApi extends Api>(
9
+ prompt: UserMessage,
10
+ context: AgentContext,
11
+ config: AgentLoopConfig<TApi>,
12
+ signal?: AbortSignal,
13
+ ): EventStream<AgentEvent, AgentContext["messages"]> {
14
+
15
+ // Convert AgentContext to Context for stream
16
+ // Use a copy of messages to avoid mutating the original context
17
+ const stream = new EventStream<AgentEvent, AgentContext["messages"]>();
18
+
19
+ // Run the prompt async
20
+ (async () => {
21
+ // Track new messages generated during this prompt
22
+ const newMessages: AgentContext["messages"] = [];
23
+ // Create user message for the prompt
24
+ const messages = [...context.messages, prompt];
25
+ newMessages.push(prompt);
26
+
27
+ stream.push({ type: "agent_start" });
28
+ stream.push({ type: "turn_start" });
29
+ stream.push({ type: "message_start", message: prompt });
30
+ stream.push({ type: "message_end", message: prompt });
31
+
32
+ // Update context with new messages
33
+ const currentContext: AgentContext = {
34
+ ...context,
35
+ messages,
36
+ };
37
+
38
+ // Keep looping while we have tool calls or queued messages
39
+ let hasMoreToolCalls = true;
40
+ let firstTurn = true;
41
+ let queuedMessages: QueuedMessage<any>[] = [];
42
+
43
+ // Safely get queued messages, handling any errors
44
+ try {
45
+ queuedMessages = (await config.getQueuedMessages?.()) || [];
46
+ } catch (error) {
47
+ console.warn("Error getting queued messages:", error instanceof Error ? error.message : String(error));
48
+ // Continue with empty queued messages
49
+ }
50
+
51
+ while (hasMoreToolCalls || queuedMessages.length > 0) {
52
+ if (!firstTurn) {
53
+ stream.push({ type: "turn_start" });
54
+ } else {
55
+ firstTurn = false;
56
+ }
57
+
58
+ // Process queued messages first (inject before next assistant response)
59
+ if (queuedMessages.length > 0) {
60
+ for (const { original, llm } of queuedMessages) {
61
+ stream.push({ type: "message_start", message: original });
62
+ stream.push({ type: "message_end", message: original });
63
+ if (llm) {
64
+ currentContext.messages.push(llm);
65
+ newMessages.push(llm);
66
+ }
67
+ }
68
+ queuedMessages = [];
69
+ }
70
+
71
+ // Stream assistant response
72
+ const {finalAssistantMessage, finalMessage} = await streamAssistantResponse(currentContext, config, signal, stream);
73
+ newMessages.push(finalMessage);
74
+ currentContext.messages.push(finalMessage); // ✅ Add to context so LLM sees its previous responses!
75
+
76
+ // Check if assistant message is valid
77
+ if (!finalAssistantMessage) {
78
+ // This should never happen, but handle it gracefully
79
+ stream.push({ type: "agent_end", messages: newMessages, status: "error" });
80
+ stream.end(newMessages);
81
+ return;
82
+ }
83
+
84
+ if(finalAssistantMessage.stopReason === 'aborted' || finalAssistantMessage.stopReason === "error"){
85
+ stream.push({ type: "turn_end", message:finalAssistantMessage, toolResults: [] });
86
+ stream.push({
87
+ type: "agent_end",
88
+ messages: newMessages,
89
+ status: finalAssistantMessage.stopReason === 'aborted' ? "aborted" : "error"
90
+ });
91
+ stream.end(newMessages);
92
+ return;
93
+ }
94
+
95
+ // Check for tool calls
96
+ const toolCalls = finalAssistantMessage.content.filter((c) => c.type === "toolCall");
97
+ hasMoreToolCalls = toolCalls.length > 0;
98
+
99
+ const toolResults: ToolResultMessage[] = [];
100
+ if (hasMoreToolCalls) {
101
+ // Execute tool calls
102
+ toolResults.push(...(await executeToolCalls(currentContext.tools, finalAssistantMessage, signal, stream)));
103
+ currentContext.messages.push(...toolResults);
104
+ newMessages.push(...toolResults);
105
+ }
106
+ stream.push({ type: "turn_end", message: finalAssistantMessage, toolResults: toolResults });
107
+
108
+ // Get queued messages after turn completes
109
+ try {
110
+ queuedMessages = (await config.getQueuedMessages?.()) || [];
111
+ } catch (error) {
112
+ console.warn("Error getting queued messages:", error instanceof Error ? error.message : String(error));
113
+ queuedMessages = [];
114
+ }
115
+ }
116
+ stream.push({ type: "agent_end", messages: newMessages, status: "completed" });
117
+ stream.end(newMessages);
118
+ })()
119
+
120
+ return stream;
121
+ }
122
+
123
+ interface StreamAssistantResult {
124
+ finalMessage: NativeAssistantMessage,
125
+ finalAssistantMessage: AssistantMessage
126
+ }
127
+
128
+ // Helper functions
129
+ async function streamAssistantResponse<TApi extends Api>(
130
+ context: AgentContext,
131
+ config: AgentLoopConfig<TApi>,
132
+ signal: AbortSignal | undefined,
133
+ stream: EventStream<AgentEvent, AgentContext["messages"]>,
134
+ ): Promise<StreamAssistantResult> {
135
+
136
+ // Convert AgentContext to Context for stream
137
+ // Use a copy of messages to avoid mutating the original context
138
+
139
+ const processedMessages = config.preprocessor
140
+ ? await config.preprocessor(context.messages, signal)
141
+ : [...context.messages];
142
+
143
+ const processedContext: Context = {
144
+ systemPrompt: context.systemPrompt,
145
+ messages: [...processedMessages],
146
+ tools: context.tools
147
+ }
148
+
149
+ const response = streamLLM(config.model, processedContext, {...config.providerOptions , signal});
150
+
151
+ let partialMessage: AssistantMessage | null = null;
152
+ let addedPartial = false;
153
+
154
+ for await (const event of response) {
155
+ switch (event.type) {
156
+ case "start":
157
+ partialMessage = event.partial;
158
+ // context.messages.push(partialMessage);
159
+ addedPartial = true;
160
+ stream.push({ type: "message_start", message: { ...partialMessage } });
161
+ break;
162
+
163
+ case "text_start":
164
+ case "text_delta":
165
+ case "text_end":
166
+ case "thinking_start":
167
+ case "thinking_delta":
168
+ case "thinking_end":
169
+ case "toolcall_start":
170
+ case "toolcall_delta":
171
+ case "toolcall_end":
172
+ if (partialMessage) {
173
+ partialMessage = event.partial;
174
+ // context.messages[context.messages.length - 1] = partialMessage;
175
+ stream.push({ type: "message_update", assistantMessageEvent: event, message: { ...partialMessage } });
176
+ }
177
+ break;
178
+
179
+ case "done":
180
+ case "error": {
181
+ const finalAssistantMessage = event.type === 'error' ? event.error : event.message
182
+ if (!addedPartial) {
183
+ stream.push({ type: "message_start", message: { ...finalAssistantMessage } });
184
+ }
185
+ stream.push({ type: "message_end", message: finalAssistantMessage });
186
+
187
+ const finalMessage = await response.result();
188
+ return {finalMessage, finalAssistantMessage}
189
+ }
190
+ }
191
+ }
192
+ const finalMessage = await response.result();
193
+ return {finalAssistantMessage: partialMessage!, finalMessage}
194
+ }
195
+
196
+ async function executeToolCalls<T>(
197
+ tools: readonly AgentTool<any, T>[] | undefined,
198
+ assistantMessage: AssistantMessage,
199
+ signal: AbortSignal | undefined,
200
+ stream: EventStream<AgentEvent, AgentContext["messages"]>,
201
+ ): Promise<ToolResultMessage<T>[]> {
202
+
203
+ const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
204
+ const results: ToolResultMessage<any>[] = [];
205
+
206
+ for (const toolCall of toolCalls) {
207
+ const tool = tools?.find((t) => t.name === toolCall.name);
208
+
209
+ stream.push({
210
+ type: "tool_execution_start",
211
+ toolCallId: toolCall.id!,
212
+ toolName: toolCall.name,
213
+ args: toolCall.arguments,
214
+ });
215
+ let resultOrError: AgentToolResult<T> | string;
216
+ let isError = false;
217
+
218
+ let errorDetails: ToolResultMessage["error"] | undefined;
219
+
220
+ try {
221
+ if (!tool) {
222
+ const availableTools = tools?.map((t) => t.name).join(", ") || "none";
223
+ throw new Error(
224
+ `Tool "${toolCall.name}" not found. Available tools: ${availableTools}`
225
+ );
226
+ }
227
+
228
+ // Validate arguments using shared validation function
229
+ const validatedArgs = validateToolArguments(tool, toolCall);
230
+
231
+ // Execute with validated, typed arguments
232
+ resultOrError = await tool.execute(toolCall.id!, validatedArgs, signal);
233
+ } catch (e) {
234
+ resultOrError = e instanceof Error ? e.message : String(e);
235
+ isError = true;
236
+ // Preserve full error details for debugging
237
+ if (e instanceof Error) {
238
+ errorDetails = {
239
+ message: e.message,
240
+ name: e.name,
241
+ stack: e.stack,
242
+ };
243
+ }
244
+ }
245
+
246
+ stream.push({
247
+ type: "tool_execution_end",
248
+ toolCallId: toolCall.id!,
249
+ toolName: toolCall.name,
250
+ result: resultOrError,
251
+ isError,
252
+ });
253
+
254
+ // Convert result to content blocks
255
+ const content: ToolResultMessage<T>["content"] =
256
+ typeof resultOrError === "string" ? [{ type: "text", content: resultOrError }] : resultOrError.content;
257
+
258
+ const toolResultMessage: ToolResultMessage<T> = {
259
+ role: "toolResult",
260
+ toolCallId: toolCall.id,
261
+ toolName: toolCall.name,
262
+ content,
263
+ details: typeof resultOrError === "string" ? ({} as T) : resultOrError.details,
264
+ isError,
265
+ error: errorDetails,
266
+ timestamp: Date.now(),
267
+ };
268
+
269
+ results.push(toolResultMessage);
270
+ stream.push({ type: "message_start", message: toolResultMessage });
271
+ stream.push({ type: "message_end", message: toolResultMessage });
272
+ }
273
+
274
+ return results;
275
+ }
@@ -0,0 +1,80 @@
1
+ import { Message, Tool, UserTextContent, UserImageContent, UserFileContent, ToolResultMessage, AssistantMessageEvent, AssistantMessage, Model, OptionsForApi, Api, UserMessage } from "../types";
2
+ import type { Static, TSchema } from "@sinclair/typebox";
3
+
4
+
5
+ export interface AgentToolResult<T> {
6
+ // Content blocks supporting text and images
7
+ content: (UserTextContent | UserImageContent | UserFileContent)[];
8
+ // Details to be displayed in a UI or logged
9
+ details: T;
10
+ }
11
+
12
+ // AgentTool extends Tool but adds the execute function
13
+ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any> extends Tool<TParameters> {
14
+ // A human-readable label for the tool to be displayed in UI
15
+ label: string;
16
+ execute: (
17
+ toolCallId: string,
18
+ params: Static<TParameters>,
19
+ signal?: AbortSignal,
20
+ ) => Promise<AgentToolResult<TDetails>>;
21
+ }
22
+
23
+ // AgentContext is like Context but uses AgentTool
24
+ export interface AgentContext {
25
+ systemPrompt?: string;
26
+ messages: Message[];
27
+ tools?: readonly AgentTool<any>[];
28
+ }
29
+
30
+
31
+ // Event types
32
+ export type AgentEvent =
33
+ // Emitted when the agent starts. An agent can emit multiple turns
34
+ | { type: "agent_start" }
35
+ // Emitted when a turn starts. A turn can emit an optional user message (initial prompt), an assistant message (response) and multiple tool result messages
36
+ | { type: "turn_start" }
37
+ // Emitted when a user, assistant or tool result message starts
38
+ | { type: "message_start"; message: AssistantMessage | UserMessage | ToolResultMessage }
39
+ // Emitted when an asssitant messages is updated due to streaming
40
+ | { type: "message_update"; assistantMessageEvent: AssistantMessageEvent; message: AssistantMessage }
41
+ // Emitted when a user, assistant or tool result message is complete
42
+ | { type: "message_end"; message: AssistantMessage | UserMessage | ToolResultMessage }
43
+ // Emitted when a tool execution starts
44
+ | { type: "tool_execution_start"; toolCallId: string; toolName: string; args: any }
45
+ // Emitted when a tool execution completes
46
+ | {
47
+ type: "tool_execution_end";
48
+ toolCallId: string;
49
+ toolName: string;
50
+ result: AgentToolResult<any> | string;
51
+ isError: boolean;
52
+ }
53
+ // Emitted when a full turn completes
54
+ | { type: "turn_end"; message: AssistantMessage; toolResults: ToolResultMessage[] }
55
+ // Emitted when the agent has completed all its turns. All messages from every turn are
56
+ // contained in messages, which can be appended to the context
57
+ | { type: "agent_end"; messages: AgentContext["messages"]; status: "completed" | "aborted" | "error" };
58
+
59
+ // Queued message with optional LLM representation
60
+ export interface QueuedMessage<TApp = Message> {
61
+ original: TApp; // Original message for UI events
62
+ llm?: Message; // Optional transformed message for loop context (undefined if filtered)
63
+ }
64
+
65
+ export type ReasoningEffort = "minimal" | "low" | "medium" | "high";
66
+
67
+ export interface SimpleProviderOptions {
68
+ reasoning?: ReasoningEffort;
69
+ signal?: AbortSignal;
70
+ apiKey?: string;
71
+ maxTokens?: number;
72
+ }
73
+
74
+ // Configuration for agent loop execution
75
+ export interface AgentLoopConfig<TApi extends Api> {
76
+ model: Model<TApi>;
77
+ preprocessor?: (messages: AgentContext["messages"], signal?: AbortSignal) => Promise<AgentContext["messages"]>;
78
+ providerOptions: OptionsForApi<TApi>,
79
+ getQueuedMessages?: <T>() => Promise<QueuedMessage<T>[]>;
80
+ }
package/src/index.ts ADDED
@@ -0,0 +1,72 @@
1
+ // ============================================================================
2
+ // Main Functions
3
+ // ============================================================================
4
+ export { stream } from "./stream";
5
+ export { agentLoop } from "./agent/agent-loop";
6
+
7
+ // ============================================================================
8
+ // Core Types
9
+ // ============================================================================
10
+ export type {
11
+ // Message types
12
+ Message,
13
+ UserMessage,
14
+ ToolResultMessage,
15
+ NativeAssistantMessage,
16
+ NativeOpenAIMessage,
17
+
18
+ // Content types
19
+ UserTextContent,
20
+ UserImageContent,
21
+ UserFileContent,
22
+ AssistantTextContent,
23
+ AssistantThinkingContent,
24
+ AssistantToolCall,
25
+ AbstractedImageContent,
26
+
27
+ // Context and Configuration
28
+ Context,
29
+ Tool,
30
+ Api,
31
+ Model,
32
+
33
+ // Streaming types
34
+ AssistantMessage,
35
+ AssistantMessageEvent,
36
+ Usage,
37
+ StopReason,
38
+
39
+ // Provider options
40
+ OptionsForApi,
41
+ StreamFunction,
42
+ } from "./types";
43
+
44
+ // ============================================================================
45
+ // Agent Types
46
+ // ============================================================================
47
+ export type {
48
+ // Agent-specific types
49
+ AgentContext,
50
+ AgentTool,
51
+ AgentToolResult,
52
+ AgentEvent,
53
+ AgentLoopConfig,
54
+ QueuedMessage,
55
+ } from "./agent/types";
56
+
57
+ // ============================================================================
58
+ // Models and Registry
59
+ // ============================================================================
60
+ export { MODELS } from "./models.generated";
61
+ export { calculateCost } from "./models";
62
+
63
+ // ============================================================================
64
+ // Utilities
65
+ // ============================================================================
66
+ export { defineTool } from "./types";
67
+ export type { ToolName, ToolNames } from "./types";
68
+
69
+ // ============================================================================
70
+ // Event Streams
71
+ // ============================================================================
72
+ export { EventStream, AssistantMessageEventStream } from "./utils/event-stream";