@yourgpt/llm-sdk 1.0.0 → 1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 YourGPT
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 CHANGED
@@ -5,7 +5,13 @@ Multi-provider LLM SDK with streaming. One API, any provider.
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install @yourgpt/llm-sdk
8
+ npm install @yourgpt/llm-sdk openai
9
+ ```
10
+
11
+ For Anthropic, install `@anthropic-ai/sdk` instead:
12
+
13
+ ```bash
14
+ npm install @yourgpt/llm-sdk @anthropic-ai/sdk
9
15
  ```
10
16
 
11
17
  ## Quick Start
@@ -18,7 +24,7 @@ export async function POST(req: Request) {
18
24
  const { messages } = await req.json();
19
25
 
20
26
  const result = await streamText({
21
- model: openai("gpt-5"),
27
+ model: openai("gpt-4o"),
22
28
  system: "You are a helpful assistant.",
23
29
  messages,
24
30
  });
@@ -36,16 +42,16 @@ import { google } from "@yourgpt/llm-sdk/google";
36
42
  import { xai } from "@yourgpt/llm-sdk/xai";
37
43
 
38
44
  // OpenAI
39
- await streamText({ model: openai("gpt-5"), messages });
45
+ await streamText({ model: openai("gpt-4o"), messages });
40
46
 
41
47
  // Anthropic
42
48
  await streamText({ model: anthropic("claude-sonnet-4-20250514"), messages });
43
49
 
44
- // Google
50
+ // Google Gemini (uses OpenAI-compatible API)
45
51
  await streamText({ model: google("gemini-2.0-flash"), messages });
46
52
 
47
- // xAI
48
- await streamText({ model: xai("grok-3"), messages });
53
+ // xAI Grok (uses OpenAI-compatible API)
54
+ await streamText({ model: xai("grok-3-fast-beta"), messages });
49
55
  ```
50
56
 
51
57
  ## Server-Side Tools
@@ -56,7 +62,7 @@ import { openai } from "@yourgpt/llm-sdk/openai";
56
62
  import { z } from "zod";
57
63
 
58
64
  const result = await streamText({
59
- model: openai("gpt-5"),
65
+ model: openai("gpt-4o"),
60
66
  messages,
61
67
  tools: {
62
68
  getWeather: tool({
@@ -77,14 +83,16 @@ return result.toDataStreamResponse();
77
83
 
78
84
  ## Supported Providers
79
85
 
80
- | Provider | Import |
81
- | ------------- | ---------------------------- |
82
- | OpenAI | `@yourgpt/llm-sdk/openai` |
83
- | Anthropic | `@yourgpt/llm-sdk/anthropic` |
84
- | Google Gemini | `@yourgpt/llm-sdk/google` |
85
- | xAI (Grok) | `@yourgpt/llm-sdk/xai` |
86
- | Ollama | `@yourgpt/llm-sdk/ollama` |
87
- | Azure OpenAI | `@yourgpt/llm-sdk/azure` |
86
+ | Provider | Import | SDK Required |
87
+ | ------------- | ---------------------------- | ------------------- |
88
+ | OpenAI | `@yourgpt/llm-sdk/openai` | `openai` |
89
+ | Anthropic | `@yourgpt/llm-sdk/anthropic` | `@anthropic-ai/sdk` |
90
+ | Google Gemini | `@yourgpt/llm-sdk/google` | `openai` |
91
+ | xAI (Grok) | `@yourgpt/llm-sdk/xai` | `openai` |
92
+ | Ollama | `@yourgpt/llm-sdk/ollama` | `openai` |
93
+ | Azure OpenAI | `@yourgpt/llm-sdk/azure` | `openai` |
94
+
95
+ > **Note:** OpenAI, Google, xAI, Ollama, and Azure all use the `openai` SDK because they have OpenAI-compatible APIs. Only Anthropic requires its native SDK for full feature support.
88
96
 
89
97
  ## Documentation
90
98
 
package/dist/index.js CHANGED
@@ -1393,6 +1393,21 @@ function formatMessagesForXAI(messages) {
1393
1393
 
1394
1394
  // src/providers/google/provider.ts
1395
1395
  var GOOGLE_MODELS = {
1396
+ // Gemini 2.5 (Experimental)
1397
+ "gemini-2.5-pro-preview-05-06": {
1398
+ vision: true,
1399
+ tools: true,
1400
+ audio: true,
1401
+ video: true,
1402
+ maxTokens: 1048576
1403
+ },
1404
+ "gemini-2.5-flash-preview-05-20": {
1405
+ vision: true,
1406
+ tools: true,
1407
+ audio: true,
1408
+ video: true,
1409
+ maxTokens: 1048576
1410
+ },
1396
1411
  // Gemini 2.0
1397
1412
  "gemini-2.0-flash": {
1398
1413
  vision: true,
@@ -1408,6 +1423,13 @@ var GOOGLE_MODELS = {
1408
1423
  video: true,
1409
1424
  maxTokens: 1048576
1410
1425
  },
1426
+ "gemini-2.0-flash-lite": {
1427
+ vision: true,
1428
+ tools: true,
1429
+ audio: false,
1430
+ video: false,
1431
+ maxTokens: 1048576
1432
+ },
1411
1433
  "gemini-2.0-flash-thinking-exp": {
1412
1434
  vision: true,
1413
1435
  tools: false,
@@ -1454,11 +1476,15 @@ var GOOGLE_MODELS = {
1454
1476
  };
1455
1477
  function google(modelId, options = {}) {
1456
1478
  const apiKey = options.apiKey ?? process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
1479
+ const baseURL = options.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
1457
1480
  let client = null;
1458
1481
  async function getClient() {
1459
1482
  if (!client) {
1460
- const { GoogleGenerativeAI } = await import('@google/generative-ai');
1461
- client = new GoogleGenerativeAI(apiKey);
1483
+ const { default: OpenAI } = await import('openai');
1484
+ client = new OpenAI({
1485
+ apiKey,
1486
+ baseURL
1487
+ });
1462
1488
  }
1463
1489
  return client;
1464
1490
  }
@@ -1478,219 +1504,176 @@ function google(modelId, options = {}) {
1478
1504
  },
1479
1505
  async doGenerate(params) {
1480
1506
  const client2 = await getClient();
1481
- const model = client2.getGenerativeModel({
1507
+ const messages = formatMessagesForGoogle(params.messages);
1508
+ const response = await client2.chat.completions.create({
1482
1509
  model: modelId,
1483
- safetySettings: options.safetySettings
1510
+ messages,
1511
+ tools: params.tools,
1512
+ temperature: params.temperature,
1513
+ max_tokens: params.maxTokens
1484
1514
  });
1485
- const { systemInstruction, contents } = formatMessagesForGemini(
1486
- params.messages
1515
+ const choice = response.choices[0];
1516
+ const message = choice.message;
1517
+ const toolCalls = (message.tool_calls ?? []).map(
1518
+ (tc) => ({
1519
+ id: tc.id,
1520
+ name: tc.function.name,
1521
+ args: JSON.parse(tc.function.arguments || "{}")
1522
+ })
1487
1523
  );
1488
- const chat = model.startChat({
1489
- history: contents.slice(0, -1),
1490
- systemInstruction: systemInstruction ? { parts: [{ text: systemInstruction }] } : void 0,
1491
- tools: params.tools ? [{ functionDeclarations: formatToolsForGemini(params.tools) }] : void 0,
1492
- generationConfig: {
1493
- temperature: params.temperature,
1494
- maxOutputTokens: params.maxTokens
1495
- }
1496
- });
1497
- const lastMessage = contents[contents.length - 1];
1498
- const result = await chat.sendMessage(lastMessage.parts);
1499
- const response = result.response;
1500
- let text = "";
1501
- const toolCalls = [];
1502
- let toolCallIndex = 0;
1503
- const candidate = response.candidates?.[0];
1504
- if (candidate?.content?.parts) {
1505
- for (const part of candidate.content.parts) {
1506
- if ("text" in part && part.text) {
1507
- text += part.text;
1508
- }
1509
- if ("functionCall" in part && part.functionCall) {
1510
- toolCalls.push({
1511
- id: `call_${toolCallIndex++}`,
1512
- name: part.functionCall.name,
1513
- args: part.functionCall.args || {}
1514
- });
1515
- }
1516
- }
1517
- }
1518
1524
  return {
1519
- text,
1525
+ text: message.content ?? "",
1520
1526
  toolCalls,
1521
- finishReason: mapFinishReason4(candidate?.finishReason),
1527
+ finishReason: mapFinishReason4(choice.finish_reason),
1522
1528
  usage: {
1523
- promptTokens: response.usageMetadata?.promptTokenCount ?? 0,
1524
- completionTokens: response.usageMetadata?.candidatesTokenCount ?? 0,
1525
- totalTokens: response.usageMetadata?.totalTokenCount ?? 0
1529
+ promptTokens: response.usage?.prompt_tokens ?? 0,
1530
+ completionTokens: response.usage?.completion_tokens ?? 0,
1531
+ totalTokens: response.usage?.total_tokens ?? 0
1526
1532
  },
1527
1533
  rawResponse: response
1528
1534
  };
1529
1535
  },
1530
1536
  async *doStream(params) {
1531
1537
  const client2 = await getClient();
1532
- const model = client2.getGenerativeModel({
1538
+ const messages = formatMessagesForGoogle(params.messages);
1539
+ const stream = await client2.chat.completions.create({
1533
1540
  model: modelId,
1534
- safetySettings: options.safetySettings
1541
+ messages,
1542
+ tools: params.tools,
1543
+ temperature: params.temperature,
1544
+ max_tokens: params.maxTokens,
1545
+ stream: true
1535
1546
  });
1536
- const { systemInstruction, contents } = formatMessagesForGemini(
1537
- params.messages
1538
- );
1539
- const chat = model.startChat({
1540
- history: contents.slice(0, -1),
1541
- systemInstruction: systemInstruction ? { parts: [{ text: systemInstruction }] } : void 0,
1542
- tools: params.tools ? [{ functionDeclarations: formatToolsForGemini(params.tools) }] : void 0,
1543
- generationConfig: {
1544
- temperature: params.temperature,
1545
- maxOutputTokens: params.maxTokens
1547
+ let currentToolCall = null;
1548
+ let totalPromptTokens = 0;
1549
+ let totalCompletionTokens = 0;
1550
+ for await (const chunk of stream) {
1551
+ if (params.signal?.aborted) {
1552
+ yield { type: "error", error: new Error("Aborted") };
1553
+ return;
1546
1554
  }
1547
- });
1548
- const lastMessage = contents[contents.length - 1];
1549
- const result = await chat.sendMessageStream(lastMessage.parts);
1550
- let toolCallIndex = 0;
1551
- let promptTokens = 0;
1552
- let completionTokens = 0;
1553
- try {
1554
- for await (const chunk of result.stream) {
1555
- if (params.signal?.aborted) {
1556
- yield { type: "error", error: new Error("Aborted") };
1557
- return;
1558
- }
1559
- const candidate = chunk.candidates?.[0];
1560
- if (!candidate?.content?.parts) continue;
1561
- for (const part of candidate.content.parts) {
1562
- if ("text" in part && part.text) {
1563
- yield { type: "text-delta", text: part.text };
1564
- }
1565
- if ("functionCall" in part && part.functionCall) {
1566
- yield {
1567
- type: "tool-call",
1568
- toolCall: {
1569
- id: `call_${toolCallIndex++}`,
1570
- name: part.functionCall.name,
1571
- args: part.functionCall.args || {}
1572
- }
1555
+ const choice = chunk.choices[0];
1556
+ const delta = choice?.delta;
1557
+ if (delta?.content) {
1558
+ yield { type: "text-delta", text: delta.content };
1559
+ }
1560
+ if (delta?.tool_calls) {
1561
+ for (const tc of delta.tool_calls) {
1562
+ if (tc.id) {
1563
+ if (currentToolCall) {
1564
+ yield {
1565
+ type: "tool-call",
1566
+ toolCall: {
1567
+ id: currentToolCall.id,
1568
+ name: currentToolCall.name,
1569
+ args: JSON.parse(currentToolCall.arguments || "{}")
1570
+ }
1571
+ };
1572
+ }
1573
+ currentToolCall = {
1574
+ id: tc.id,
1575
+ name: tc.function?.name ?? "",
1576
+ arguments: tc.function?.arguments ?? ""
1573
1577
  };
1578
+ } else if (currentToolCall && tc.function?.arguments) {
1579
+ currentToolCall.arguments += tc.function.arguments;
1574
1580
  }
1575
1581
  }
1576
- if (chunk.usageMetadata) {
1577
- promptTokens = chunk.usageMetadata.promptTokenCount ?? 0;
1578
- completionTokens = chunk.usageMetadata.candidatesTokenCount ?? 0;
1579
- }
1580
- if (candidate.finishReason) {
1582
+ }
1583
+ if (choice?.finish_reason) {
1584
+ if (currentToolCall) {
1581
1585
  yield {
1582
- type: "finish",
1583
- finishReason: mapFinishReason4(candidate.finishReason),
1584
- usage: {
1585
- promptTokens,
1586
- completionTokens,
1587
- totalTokens: promptTokens + completionTokens
1586
+ type: "tool-call",
1587
+ toolCall: {
1588
+ id: currentToolCall.id,
1589
+ name: currentToolCall.name,
1590
+ args: JSON.parse(currentToolCall.arguments || "{}")
1588
1591
  }
1589
1592
  };
1593
+ currentToolCall = null;
1594
+ }
1595
+ if (chunk.usage) {
1596
+ totalPromptTokens = chunk.usage.prompt_tokens;
1597
+ totalCompletionTokens = chunk.usage.completion_tokens;
1590
1598
  }
1599
+ yield {
1600
+ type: "finish",
1601
+ finishReason: mapFinishReason4(choice.finish_reason),
1602
+ usage: {
1603
+ promptTokens: totalPromptTokens,
1604
+ completionTokens: totalCompletionTokens,
1605
+ totalTokens: totalPromptTokens + totalCompletionTokens
1606
+ }
1607
+ };
1591
1608
  }
1592
- } catch (error) {
1593
- yield {
1594
- type: "error",
1595
- error: error instanceof Error ? error : new Error(String(error))
1596
- };
1597
1609
  }
1598
1610
  }
1599
1611
  };
1600
1612
  }
1601
1613
  function mapFinishReason4(reason) {
1602
1614
  switch (reason) {
1603
- case "STOP":
1615
+ case "stop":
1604
1616
  return "stop";
1605
- case "MAX_TOKENS":
1617
+ case "length":
1606
1618
  return "length";
1607
- case "SAFETY":
1619
+ case "tool_calls":
1620
+ case "function_call":
1621
+ return "tool-calls";
1622
+ case "content_filter":
1608
1623
  return "content-filter";
1609
1624
  default:
1610
1625
  return "unknown";
1611
1626
  }
1612
1627
  }
1613
- function formatMessagesForGemini(messages) {
1614
- let systemInstruction = "";
1615
- const contents = [];
1616
- for (const msg of messages) {
1617
- if (msg.role === "system") {
1618
- systemInstruction += (systemInstruction ? "\n" : "") + msg.content;
1619
- continue;
1620
- }
1621
- const parts = [];
1622
- if (msg.role === "user") {
1623
- if (typeof msg.content === "string") {
1624
- parts.push({ text: msg.content });
1625
- } else {
1626
- for (const part of msg.content) {
1627
- if (part.type === "text") {
1628
- parts.push({ text: part.text });
1629
- } else if (part.type === "image") {
1630
- const imageData = typeof part.image === "string" ? part.image : Buffer.from(part.image).toString("base64");
1631
- const base64 = imageData.startsWith("data:") ? imageData.split(",")[1] : imageData;
1632
- parts.push({
1633
- inlineData: {
1634
- mimeType: part.mimeType ?? "image/png",
1635
- data: base64
1636
- }
1637
- });
1638
- }
1628
+ function formatMessagesForGoogle(messages) {
1629
+ return messages.map((msg) => {
1630
+ switch (msg.role) {
1631
+ case "system":
1632
+ return { role: "system", content: msg.content };
1633
+ case "user":
1634
+ if (typeof msg.content === "string") {
1635
+ return { role: "user", content: msg.content };
1639
1636
  }
1640
- }
1641
- contents.push({ role: "user", parts });
1642
- } else if (msg.role === "assistant") {
1643
- if (msg.content) {
1644
- parts.push({ text: msg.content });
1645
- }
1646
- if (msg.toolCalls?.length) {
1647
- for (const tc of msg.toolCalls) {
1648
- parts.push({
1649
- functionCall: {
1637
+ return {
1638
+ role: "user",
1639
+ content: msg.content.map((part) => {
1640
+ if (part.type === "text") {
1641
+ return { type: "text", text: part.text };
1642
+ }
1643
+ if (part.type === "image") {
1644
+ const imageData = typeof part.image === "string" ? part.image : Buffer.from(part.image).toString("base64");
1645
+ const url = imageData.startsWith("data:") ? imageData : `data:${part.mimeType ?? "image/png"};base64,${imageData}`;
1646
+ return { type: "image_url", image_url: { url, detail: "auto" } };
1647
+ }
1648
+ return { type: "text", text: "" };
1649
+ })
1650
+ };
1651
+ case "assistant":
1652
+ const assistantMsg = {
1653
+ role: "assistant",
1654
+ content: msg.content
1655
+ };
1656
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
1657
+ assistantMsg.tool_calls = msg.toolCalls.map((tc) => ({
1658
+ id: tc.id,
1659
+ type: "function",
1660
+ function: {
1650
1661
  name: tc.name,
1651
- args: tc.args
1662
+ arguments: JSON.stringify(tc.args)
1652
1663
  }
1653
- });
1664
+ }));
1654
1665
  }
1655
- }
1656
- if (parts.length > 0) {
1657
- contents.push({ role: "model", parts });
1658
- }
1659
- } else if (msg.role === "tool") {
1660
- contents.push({
1661
- role: "user",
1662
- parts: [
1663
- {
1664
- functionResponse: {
1665
- name: "tool",
1666
- // Gemini doesn't track by ID
1667
- response: JSON.parse(msg.content || "{}")
1668
- }
1669
- }
1670
- ]
1671
- });
1672
- }
1673
- }
1674
- if (contents.length === 0 || contents[0].role !== "user") {
1675
- contents.unshift({ role: "user", parts: [{ text: "" }] });
1676
- }
1677
- const merged = [];
1678
- for (const content of contents) {
1679
- const last = merged[merged.length - 1];
1680
- if (last && last.role === content.role) {
1681
- last.parts.push(...content.parts);
1682
- } else {
1683
- merged.push({ ...content, parts: [...content.parts] });
1666
+ return assistantMsg;
1667
+ case "tool":
1668
+ return {
1669
+ role: "tool",
1670
+ tool_call_id: msg.toolCallId,
1671
+ content: msg.content
1672
+ };
1673
+ default:
1674
+ return msg;
1684
1675
  }
1685
- }
1686
- return { systemInstruction, contents: merged };
1687
- }
1688
- function formatToolsForGemini(tools) {
1689
- return tools.map((t) => ({
1690
- name: t.function.name,
1691
- description: t.function.description,
1692
- parameters: t.function.parameters
1693
- }));
1676
+ });
1694
1677
  }
1695
1678
 
1696
1679
  // src/adapters/base.ts
@@ -2596,7 +2579,7 @@ function messageToGeminiContent(msg) {
2596
2579
  parts
2597
2580
  };
2598
2581
  }
2599
- function formatToolsForGemini2(actions) {
2582
+ function formatToolsForGemini(actions) {
2600
2583
  if (!actions || actions.length === 0) return void 0;
2601
2584
  return {
2602
2585
  functionDeclarations: actions.map((action) => ({
@@ -2682,7 +2665,7 @@ var GoogleAdapter = class {
2682
2665
  mergedContents.push({ ...content, parts: [...content.parts] });
2683
2666
  }
2684
2667
  }
2685
- const tools = formatToolsForGemini2(request.actions);
2668
+ const tools = formatToolsForGemini(request.actions);
2686
2669
  const messageId = core.generateMessageId();
2687
2670
  yield { type: "message:start", id: messageId };
2688
2671
  try {
@@ -2788,7 +2771,7 @@ var GoogleAdapter = class {
2788
2771
  mergedContents.push({ ...content, parts: [...content.parts] });
2789
2772
  }
2790
2773
  }
2791
- const tools = formatToolsForGemini2(request.actions);
2774
+ const tools = formatToolsForGemini(request.actions);
2792
2775
  const chat = model.startChat({
2793
2776
  history: mergedContents.slice(0, -1),
2794
2777
  systemInstruction: systemInstruction ? { parts: [{ text: systemInstruction }] } : void 0,