@agentier/openai 0.1.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.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * OpenAI provider for the agenti framework.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ export { openai } from './provider';
7
+ export type { OpenAIProviderConfig } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,213 @@
1
+ // src/mapper.ts
2
+ function toOpenAIMessages(messages) {
3
+ return messages.map((msg) => {
4
+ const oaiMsg = {
5
+ role: msg.role,
6
+ content: msg.content
7
+ };
8
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
9
+ oaiMsg.tool_calls = msg.toolCalls.map((tc) => ({
10
+ id: tc.id,
11
+ type: "function",
12
+ function: {
13
+ name: tc.name,
14
+ arguments: JSON.stringify(tc.arguments)
15
+ }
16
+ }));
17
+ }
18
+ if (msg.toolCallId) {
19
+ oaiMsg.tool_call_id = msg.toolCallId;
20
+ }
21
+ if (msg.name) {
22
+ oaiMsg.name = msg.name;
23
+ }
24
+ return oaiMsg;
25
+ });
26
+ }
27
+ function toOpenAITools(tools) {
28
+ return tools.map((t) => ({
29
+ type: "function",
30
+ function: {
31
+ name: t.name,
32
+ description: t.description,
33
+ parameters: t.parameters
34
+ }
35
+ }));
36
+ }
37
+ function fromOpenAIToolCalls(toolCalls) {
38
+ if (!toolCalls)
39
+ return [];
40
+ return toolCalls.map((tc) => ({
41
+ id: tc.id,
42
+ name: tc.function.name,
43
+ arguments: JSON.parse(tc.function.arguments)
44
+ }));
45
+ }
46
+
47
+ // src/provider.ts
48
+ function openai(config) {
49
+ const baseUrl = (config.baseUrl ?? "https://api.openai.com/v1").replace(/\/$/, "");
50
+ const fetchFn = config.fetch ?? globalThis.fetch;
51
+ function buildHeaders() {
52
+ const headers = {
53
+ "Content-Type": "application/json",
54
+ Authorization: `Bearer ${config.apiKey}`,
55
+ ...config.defaultHeaders
56
+ };
57
+ if (config.organization) {
58
+ headers["OpenAI-Organization"] = config.organization;
59
+ }
60
+ return headers;
61
+ }
62
+ function buildBody(params, stream = false) {
63
+ const body = {
64
+ model: params.model,
65
+ messages: toOpenAIMessages(params.messages),
66
+ stream
67
+ };
68
+ if (params.tools && params.tools.length > 0) {
69
+ body.tools = toOpenAITools(params.tools);
70
+ }
71
+ if (params.temperature !== undefined)
72
+ body.temperature = params.temperature;
73
+ if (params.topP !== undefined)
74
+ body.top_p = params.topP;
75
+ if (params.maxOutputTokens !== undefined)
76
+ body.max_tokens = params.maxOutputTokens;
77
+ if (params.responseFormat) {
78
+ body.response_format = params.responseFormat;
79
+ }
80
+ return body;
81
+ }
82
+ const provider = {
83
+ name: "openai",
84
+ async chat(params) {
85
+ const response = await fetchFn(`${baseUrl}/chat/completions`, {
86
+ method: "POST",
87
+ headers: buildHeaders(),
88
+ body: JSON.stringify(buildBody(params, false)),
89
+ signal: params.signal
90
+ });
91
+ if (!response.ok) {
92
+ const errorBody = await response.text();
93
+ throw new Error(`OpenAI API error (${response.status}): ${errorBody}`);
94
+ }
95
+ const data = await response.json();
96
+ const choice = data.choices[0];
97
+ return {
98
+ content: choice.message.content,
99
+ toolCalls: fromOpenAIToolCalls(choice.message.tool_calls),
100
+ usage: {
101
+ inputTokens: data.usage?.prompt_tokens ?? 0,
102
+ outputTokens: data.usage?.completion_tokens ?? 0
103
+ },
104
+ raw: data
105
+ };
106
+ },
107
+ async* stream(params) {
108
+ const response = await fetchFn(`${baseUrl}/chat/completions`, {
109
+ method: "POST",
110
+ headers: buildHeaders(),
111
+ body: JSON.stringify(buildBody(params, true)),
112
+ signal: params.signal
113
+ });
114
+ if (!response.ok) {
115
+ const errorBody = await response.text();
116
+ throw new Error(`OpenAI API error (${response.status}): ${errorBody}`);
117
+ }
118
+ const reader = response.body?.getReader();
119
+ if (!reader)
120
+ throw new Error("No response body");
121
+ const decoder = new TextDecoder;
122
+ let buffer = "";
123
+ let fullContent = "";
124
+ const toolCallBuffers = new Map;
125
+ let inputTokens = 0;
126
+ let outputTokens = 0;
127
+ try {
128
+ while (true) {
129
+ const { done, value } = await reader.read();
130
+ if (done)
131
+ break;
132
+ buffer += decoder.decode(value, { stream: true });
133
+ const lines = buffer.split(`
134
+ `);
135
+ buffer = lines.pop() ?? "";
136
+ for (const line of lines) {
137
+ const trimmed = line.trim();
138
+ if (!trimmed || !trimmed.startsWith("data: "))
139
+ continue;
140
+ const data = trimmed.slice(6);
141
+ if (data === "[DONE]")
142
+ continue;
143
+ try {
144
+ const parsed = JSON.parse(data);
145
+ const delta = parsed.choices?.[0]?.delta;
146
+ if (!delta)
147
+ continue;
148
+ if (delta.content) {
149
+ fullContent += delta.content;
150
+ yield { type: "token", text: delta.content };
151
+ }
152
+ if (delta.tool_calls) {
153
+ for (const tc of delta.tool_calls) {
154
+ const idx = tc.index ?? 0;
155
+ if (tc.id) {
156
+ toolCallBuffers.set(idx, {
157
+ id: tc.id,
158
+ name: tc.function?.name ?? "",
159
+ args: tc.function?.arguments ?? ""
160
+ });
161
+ yield {
162
+ type: "tool_call_start",
163
+ id: tc.id,
164
+ name: tc.function?.name ?? ""
165
+ };
166
+ } else {
167
+ const existing = toolCallBuffers.get(idx);
168
+ if (existing && tc.function?.arguments) {
169
+ existing.args += tc.function.arguments;
170
+ yield {
171
+ type: "tool_call_delta",
172
+ id: existing.id,
173
+ argumentsDelta: tc.function.arguments
174
+ };
175
+ }
176
+ }
177
+ }
178
+ }
179
+ if (parsed.usage) {
180
+ inputTokens = parsed.usage.prompt_tokens ?? inputTokens;
181
+ outputTokens = parsed.usage.completion_tokens ?? outputTokens;
182
+ }
183
+ } catch {}
184
+ }
185
+ }
186
+ } finally {
187
+ reader.releaseLock();
188
+ }
189
+ const toolCalls = [];
190
+ for (const [, tc] of toolCallBuffers) {
191
+ const call = {
192
+ id: tc.id,
193
+ name: tc.name,
194
+ arguments: tc.args ? JSON.parse(tc.args) : {}
195
+ };
196
+ toolCalls.push(call);
197
+ yield { type: "tool_call_end", id: tc.id, call };
198
+ }
199
+ yield {
200
+ type: "done",
201
+ response: {
202
+ content: fullContent || null,
203
+ toolCalls,
204
+ usage: { inputTokens, outputTokens }
205
+ }
206
+ };
207
+ }
208
+ };
209
+ return provider;
210
+ }
211
+ export {
212
+ openai
213
+ };
@@ -0,0 +1,53 @@
1
+ import type { Message, ToolCall, ToolJsonSchema } from '@agentier/core';
2
+ /** Wire format for an OpenAI chat message. */
3
+ export interface OpenAIMessage {
4
+ role: 'system' | 'user' | 'assistant' | 'tool';
5
+ content: string | null;
6
+ tool_calls?: OpenAIToolCall[];
7
+ tool_call_id?: string;
8
+ name?: string;
9
+ }
10
+ /** Wire format for an OpenAI tool call within an assistant message. */
11
+ export interface OpenAIToolCall {
12
+ id: string;
13
+ type: 'function';
14
+ function: {
15
+ name: string;
16
+ /** JSON-encoded arguments string. */
17
+ arguments: string;
18
+ };
19
+ }
20
+ /** Wire format for an OpenAI tool (function) definition. */
21
+ export interface OpenAITool {
22
+ type: 'function';
23
+ function: {
24
+ name: string;
25
+ description: string;
26
+ parameters: Record<string, unknown>;
27
+ };
28
+ }
29
+ /**
30
+ * Converts agenti `Message` objects into the OpenAI chat completions message format.
31
+ *
32
+ * Handles tool calls, tool call IDs, and optional message names.
33
+ *
34
+ * @param messages - Array of provider-agnostic messages.
35
+ * @returns Array of OpenAI-formatted messages ready for the API.
36
+ */
37
+ export declare function toOpenAIMessages(messages: Message[]): OpenAIMessage[];
38
+ /**
39
+ * Converts agenti tool schemas into the OpenAI function-calling tool format.
40
+ *
41
+ * @param tools - Array of provider-agnostic tool JSON schemas.
42
+ * @returns Array of OpenAI tool definitions.
43
+ */
44
+ export declare function toOpenAITools(tools: ToolJsonSchema[]): OpenAITool[];
45
+ /**
46
+ * Converts OpenAI tool calls from a response back into agenti `ToolCall` objects.
47
+ *
48
+ * Parses the JSON-encoded `arguments` string in each tool call.
49
+ *
50
+ * @param toolCalls - Optional array of OpenAI tool calls from the API response.
51
+ * @returns Array of agenti tool calls, or an empty array if none were provided.
52
+ */
53
+ export declare function fromOpenAIToolCalls(toolCalls?: OpenAIToolCall[]): ToolCall[];
@@ -0,0 +1,24 @@
1
+ import type { ModelProvider } from '@agentier/core';
2
+ import type { OpenAIProviderConfig } from './types';
3
+ /**
4
+ * Creates an OpenAI-compatible {@link ModelProvider}.
5
+ *
6
+ * Supports both blocking `chat` and streaming `stream` completions via the
7
+ * `/chat/completions` endpoint. Works with any OpenAI-compatible API by
8
+ * overriding `baseUrl` in the config.
9
+ *
10
+ * @param config - Provider configuration including API key and optional overrides.
11
+ * @returns A `ModelProvider` wired to the OpenAI chat completions API.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { openai } from '@agentier/provider-openai'
16
+ *
17
+ * const provider = openai({ apiKey: process.env.OPENAI_API_KEY! })
18
+ * const response = await provider.chat({
19
+ * model: 'gpt-4o',
20
+ * messages: [{ role: 'user', content: 'Hello!' }],
21
+ * })
22
+ * ```
23
+ */
24
+ export declare function openai(config: OpenAIProviderConfig): ModelProvider;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Configuration for the OpenAI provider.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * const config: OpenAIProviderConfig = {
7
+ * apiKey: process.env.OPENAI_API_KEY!,
8
+ * organization: 'org-abc123',
9
+ * }
10
+ * ```
11
+ */
12
+ export interface OpenAIProviderConfig {
13
+ /** OpenAI API key used for authentication. */
14
+ apiKey: string;
15
+ /** Base URL for API requests. Defaults to `https://api.openai.com/v1`. */
16
+ baseUrl?: string;
17
+ /** OpenAI organization ID sent via the `OpenAI-Organization` header. */
18
+ organization?: string;
19
+ /** Additional headers merged into every request. */
20
+ defaultHeaders?: Record<string, string>;
21
+ /** Custom `fetch` implementation, useful for proxies or test doubles. */
22
+ fetch?: typeof globalThis.fetch;
23
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@agentier/openai",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "scripts": {
17
+ "build": "bun build ./src/index.ts --outdir ./dist --target node",
18
+ "test": "bun test",
19
+ "typecheck": "tsc --noEmit"
20
+ },
21
+ "peerDependencies": {
22
+ "@agentier/core": "^0.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "@agentier/core": "workspace:*",
26
+ "typescript": "^5.5.0",
27
+ "@types/bun": "latest"
28
+ }
29
+ }