@yourgpt/llm-sdk 1.3.0 → 1.4.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/dist/index.js CHANGED
@@ -1363,6 +1363,12 @@ function createSSEHeaders() {
1363
1363
  "X-Accel-Buffering": "no"
1364
1364
  };
1365
1365
  }
1366
+ function createTextStreamHeaders() {
1367
+ return {
1368
+ "Content-Type": "text/plain; charset=utf-8",
1369
+ "Cache-Control": "no-cache"
1370
+ };
1371
+ }
1366
1372
  function formatSSEData(event) {
1367
1373
  return `data: ${JSON.stringify(event)}
1368
1374
 
@@ -1390,11 +1396,407 @@ function createEventStream(generator) {
1390
1396
  }
1391
1397
  });
1392
1398
  }
1393
- function createSSEResponse(generator) {
1399
+ function createSSEResponse(generator, options) {
1394
1400
  return new Response(createEventStream(generator), {
1395
- headers: createSSEHeaders()
1401
+ headers: {
1402
+ ...createSSEHeaders(),
1403
+ ...options?.headers
1404
+ }
1396
1405
  });
1397
1406
  }
1407
+ function createTextStreamResponse(generator, options) {
1408
+ const encoder = new TextEncoder();
1409
+ const stream = new ReadableStream({
1410
+ async start(controller) {
1411
+ try {
1412
+ for await (const event of generator) {
1413
+ if (event.type === "message:delta") {
1414
+ controller.enqueue(encoder.encode(event.content));
1415
+ }
1416
+ }
1417
+ } catch (error) {
1418
+ console.error("[Streaming] Text stream error:", error);
1419
+ } finally {
1420
+ controller.close();
1421
+ }
1422
+ }
1423
+ });
1424
+ return new Response(stream, {
1425
+ headers: {
1426
+ ...createTextStreamHeaders(),
1427
+ ...options?.headers
1428
+ }
1429
+ });
1430
+ }
1431
+ async function pipeSSEToResponse(generator, res, options) {
1432
+ res.setHeader("Content-Type", "text/event-stream");
1433
+ res.setHeader("Cache-Control", "no-cache, no-transform");
1434
+ res.setHeader("Connection", "keep-alive");
1435
+ res.setHeader("X-Accel-Buffering", "no");
1436
+ if (options?.headers) {
1437
+ for (const [key, value] of Object.entries(options.headers)) {
1438
+ res.setHeader(key, value);
1439
+ }
1440
+ }
1441
+ try {
1442
+ for await (const event of generator) {
1443
+ res.write(formatSSEData(event));
1444
+ }
1445
+ } catch (error) {
1446
+ const errorEvent = {
1447
+ type: "error",
1448
+ message: error instanceof Error ? error.message : "Unknown error"
1449
+ };
1450
+ res.write(formatSSEData(errorEvent));
1451
+ } finally {
1452
+ res.write("data: [DONE]\n\n");
1453
+ res.end();
1454
+ }
1455
+ }
1456
+ async function pipeTextToResponse(generator, res, options) {
1457
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
1458
+ res.setHeader("Cache-Control", "no-cache");
1459
+ if (options?.headers) {
1460
+ for (const [key, value] of Object.entries(options.headers)) {
1461
+ res.setHeader(key, value);
1462
+ }
1463
+ }
1464
+ try {
1465
+ for await (const event of generator) {
1466
+ if (event.type === "message:delta") {
1467
+ res.write(event.content);
1468
+ }
1469
+ }
1470
+ } finally {
1471
+ res.end();
1472
+ }
1473
+ }
1474
+
1475
+ // src/server/stream-result.ts
1476
+ var StreamResult = class {
1477
+ constructor(generator) {
1478
+ this.consumed = false;
1479
+ this.eventHandlers = /* @__PURE__ */ new Map();
1480
+ this.generator = generator;
1481
+ }
1482
+ // ============================================
1483
+ // Async Iteration (base pattern)
1484
+ // ============================================
1485
+ /**
1486
+ * Iterate over stream events
1487
+ *
1488
+ * @example
1489
+ * ```typescript
1490
+ * const result = runtime.stream(body);
1491
+ * for await (const event of result) {
1492
+ * if (event.type === 'message:delta') {
1493
+ * console.log(event.content);
1494
+ * }
1495
+ * }
1496
+ * ```
1497
+ */
1498
+ [Symbol.asyncIterator]() {
1499
+ this.ensureNotConsumed();
1500
+ return this.generator;
1501
+ }
1502
+ // ============================================
1503
+ // Web API Response Methods (Next.js, Cloudflare, Deno)
1504
+ // ============================================
1505
+ /**
1506
+ * Returns SSE Response for Web API frameworks
1507
+ *
1508
+ * @example
1509
+ * ```typescript
1510
+ * // Next.js App Router
1511
+ * export async function POST(req: Request) {
1512
+ * const body = await req.json();
1513
+ * return runtime.stream(body).toResponse();
1514
+ * }
1515
+ * ```
1516
+ */
1517
+ toResponse(options) {
1518
+ this.ensureNotConsumed();
1519
+ const headers = {
1520
+ ...createSSEHeaders(),
1521
+ ...options?.headers
1522
+ };
1523
+ return new Response(createEventStream(this.generator), { headers });
1524
+ }
1525
+ /**
1526
+ * Alias for toResponse() - returns SSE Response
1527
+ */
1528
+ toSSEResponse(options) {
1529
+ return this.toResponse(options);
1530
+ }
1531
+ /**
1532
+ * Returns text-only Response (no SSE events, just text content)
1533
+ *
1534
+ * @example
1535
+ * ```typescript
1536
+ * // Simple text streaming
1537
+ * return runtime.stream(body).toTextResponse();
1538
+ * ```
1539
+ */
1540
+ toTextResponse(options) {
1541
+ this.ensureNotConsumed();
1542
+ const encoder = new TextEncoder();
1543
+ const generator = this.generator;
1544
+ const stream = new ReadableStream({
1545
+ async start(controller) {
1546
+ try {
1547
+ for await (const event of generator) {
1548
+ if (event.type === "message:delta") {
1549
+ controller.enqueue(encoder.encode(event.content));
1550
+ }
1551
+ }
1552
+ } catch (error) {
1553
+ console.error("[StreamResult] Text stream error:", error);
1554
+ } finally {
1555
+ controller.close();
1556
+ }
1557
+ }
1558
+ });
1559
+ return new Response(stream, {
1560
+ headers: {
1561
+ "Content-Type": "text/plain; charset=utf-8",
1562
+ "Cache-Control": "no-cache",
1563
+ ...options?.headers
1564
+ }
1565
+ });
1566
+ }
1567
+ /**
1568
+ * Returns the underlying ReadableStream
1569
+ *
1570
+ * @example
1571
+ * ```typescript
1572
+ * const stream = runtime.stream(body).toReadableStream();
1573
+ * // Use with custom handling
1574
+ * ```
1575
+ */
1576
+ toReadableStream() {
1577
+ this.ensureNotConsumed();
1578
+ return createEventStream(this.generator);
1579
+ }
1580
+ // ============================================
1581
+ // Node.js/Express Response Methods
1582
+ // ============================================
1583
+ /**
1584
+ * Pipe SSE stream to Node.js ServerResponse
1585
+ *
1586
+ * @example
1587
+ * ```typescript
1588
+ * // Express - one-liner
1589
+ * app.post('/chat', async (req, res) => {
1590
+ * await runtime.stream(req.body).pipeToResponse(res);
1591
+ * });
1592
+ * ```
1593
+ */
1594
+ async pipeToResponse(res, options) {
1595
+ this.ensureNotConsumed();
1596
+ res.setHeader("Content-Type", "text/event-stream");
1597
+ res.setHeader("Cache-Control", "no-cache, no-transform");
1598
+ res.setHeader("Connection", "keep-alive");
1599
+ res.setHeader("X-Accel-Buffering", "no");
1600
+ if (options?.headers) {
1601
+ for (const [key, value] of Object.entries(options.headers)) {
1602
+ res.setHeader(key, value);
1603
+ }
1604
+ }
1605
+ const collected = this.createCollector();
1606
+ try {
1607
+ for await (const event of this.generator) {
1608
+ this.collectEvent(event, collected);
1609
+ this.callEventHandlers(event, collected);
1610
+ res.write(formatSSEData(event));
1611
+ }
1612
+ } catch (error) {
1613
+ const errorEvent = {
1614
+ type: "error",
1615
+ message: error instanceof Error ? error.message : "Unknown error"
1616
+ };
1617
+ res.write(formatSSEData(errorEvent));
1618
+ const errorHandler = this.eventHandlers.get("error");
1619
+ if (errorHandler) {
1620
+ errorHandler(error instanceof Error ? error : new Error(String(error)));
1621
+ }
1622
+ } finally {
1623
+ res.write("data: [DONE]\n\n");
1624
+ res.end();
1625
+ }
1626
+ const doneHandler = this.eventHandlers.get("done");
1627
+ if (doneHandler) {
1628
+ doneHandler(collected);
1629
+ }
1630
+ return collected;
1631
+ }
1632
+ /**
1633
+ * Pipe text-only stream to Node.js ServerResponse
1634
+ *
1635
+ * @example
1636
+ * ```typescript
1637
+ * // Express - text-only streaming
1638
+ * app.post('/chat', async (req, res) => {
1639
+ * await runtime.stream(req.body).pipeTextToResponse(res);
1640
+ * });
1641
+ * ```
1642
+ */
1643
+ async pipeTextToResponse(res, options) {
1644
+ this.ensureNotConsumed();
1645
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
1646
+ res.setHeader("Cache-Control", "no-cache");
1647
+ if (options?.headers) {
1648
+ for (const [key, value] of Object.entries(options.headers)) {
1649
+ res.setHeader(key, value);
1650
+ }
1651
+ }
1652
+ const collected = this.createCollector();
1653
+ try {
1654
+ for await (const event of this.generator) {
1655
+ this.collectEvent(event, collected);
1656
+ this.callEventHandlers(event, collected);
1657
+ if (event.type === "message:delta") {
1658
+ res.write(event.content);
1659
+ }
1660
+ }
1661
+ } catch (error) {
1662
+ const errorHandler = this.eventHandlers.get("error");
1663
+ if (errorHandler) {
1664
+ errorHandler(error instanceof Error ? error : new Error(String(error)));
1665
+ }
1666
+ } finally {
1667
+ res.end();
1668
+ }
1669
+ const doneHandler = this.eventHandlers.get("done");
1670
+ if (doneHandler) {
1671
+ doneHandler(collected);
1672
+ }
1673
+ return collected;
1674
+ }
1675
+ // ============================================
1676
+ // Collection Methods
1677
+ // ============================================
1678
+ /**
1679
+ * Collect all events and return final result
1680
+ *
1681
+ * @example
1682
+ * ```typescript
1683
+ * const { text, messages, toolCalls } = await runtime.stream(body).collect();
1684
+ * console.log('Response:', text);
1685
+ * ```
1686
+ */
1687
+ async collect() {
1688
+ this.ensureNotConsumed();
1689
+ const collected = this.createCollector();
1690
+ for await (const event of this.generator) {
1691
+ this.collectEvent(event, collected);
1692
+ this.callEventHandlers(event, collected);
1693
+ }
1694
+ const doneHandler = this.eventHandlers.get("done");
1695
+ if (doneHandler) {
1696
+ doneHandler(collected);
1697
+ }
1698
+ return collected;
1699
+ }
1700
+ /**
1701
+ * Get final text (convenience method)
1702
+ *
1703
+ * @example
1704
+ * ```typescript
1705
+ * const text = await runtime.stream(body).text();
1706
+ * ```
1707
+ */
1708
+ async text() {
1709
+ const result = await this.collect();
1710
+ return result.text;
1711
+ }
1712
+ on(event, handler) {
1713
+ this.eventHandlers.set(event, handler);
1714
+ return this;
1715
+ }
1716
+ // ============================================
1717
+ // Internal Methods
1718
+ // ============================================
1719
+ /**
1720
+ * Ensure stream hasn't been consumed
1721
+ */
1722
+ ensureNotConsumed() {
1723
+ if (this.consumed) {
1724
+ throw new Error(
1725
+ "StreamResult has already been consumed. Each StreamResult can only be consumed once."
1726
+ );
1727
+ }
1728
+ this.consumed = true;
1729
+ }
1730
+ /**
1731
+ * Create empty collector object
1732
+ */
1733
+ createCollector() {
1734
+ return {
1735
+ text: "",
1736
+ messages: [],
1737
+ toolCalls: [],
1738
+ requiresAction: false,
1739
+ events: []
1740
+ };
1741
+ }
1742
+ /**
1743
+ * Collect event into result
1744
+ */
1745
+ collectEvent(event, collected) {
1746
+ collected.events.push(event);
1747
+ switch (event.type) {
1748
+ case "message:delta":
1749
+ collected.text += event.content;
1750
+ break;
1751
+ case "tool_calls":
1752
+ collected.toolCalls.push(...event.toolCalls);
1753
+ break;
1754
+ case "done":
1755
+ if (event.messages) {
1756
+ collected.messages.push(...event.messages);
1757
+ }
1758
+ if (event.requiresAction) {
1759
+ collected.requiresAction = true;
1760
+ }
1761
+ break;
1762
+ }
1763
+ }
1764
+ /**
1765
+ * Call registered event handlers
1766
+ */
1767
+ callEventHandlers(event, collected) {
1768
+ switch (event.type) {
1769
+ case "message:delta": {
1770
+ const textHandler = this.eventHandlers.get("text");
1771
+ if (textHandler) {
1772
+ textHandler(event.content);
1773
+ }
1774
+ break;
1775
+ }
1776
+ case "tool_calls": {
1777
+ const toolCallHandler = this.eventHandlers.get(
1778
+ "toolCall"
1779
+ );
1780
+ if (toolCallHandler) {
1781
+ for (const toolCall of event.toolCalls) {
1782
+ toolCallHandler(toolCall);
1783
+ }
1784
+ }
1785
+ break;
1786
+ }
1787
+ case "error": {
1788
+ const errorHandler = this.eventHandlers.get("error");
1789
+ if (errorHandler) {
1790
+ errorHandler(new Error(event.message));
1791
+ }
1792
+ break;
1793
+ }
1794
+ }
1795
+ }
1796
+ };
1797
+ function createStreamResult(generator) {
1798
+ return new StreamResult(generator);
1799
+ }
1398
1800
 
1399
1801
  // src/server/runtime.ts
1400
1802
  function buildToolResultForAI(tool2, result, args) {
@@ -2464,6 +2866,89 @@ var Runtime = class {
2464
2866
  }
2465
2867
  return parameters;
2466
2868
  }
2869
+ // ============================================
2870
+ // StreamResult API (Industry Standard Pattern)
2871
+ // ============================================
2872
+ /**
2873
+ * Stream chat and return StreamResult with helper methods
2874
+ *
2875
+ * This is the recommended API for new projects. It returns a StreamResult
2876
+ * object with multiple ways to consume the response:
2877
+ * - `pipeToResponse(res)` for Express/Node.js
2878
+ * - `toResponse()` for Next.js/Web API
2879
+ * - `collect()` for non-streaming use cases
2880
+ *
2881
+ * @example
2882
+ * ```typescript
2883
+ * // Express - one-liner
2884
+ * app.post('/chat', async (req, res) => {
2885
+ * await runtime.stream(req.body).pipeToResponse(res);
2886
+ * });
2887
+ *
2888
+ * // Next.js App Router
2889
+ * export async function POST(req: Request) {
2890
+ * const body = await req.json();
2891
+ * return runtime.stream(body).toResponse();
2892
+ * }
2893
+ *
2894
+ * // With event handlers
2895
+ * const result = runtime.stream(body)
2896
+ * .on('text', (text) => console.log(text))
2897
+ * .on('done', (result) => console.log('Done:', result.text));
2898
+ * await result.pipeToResponse(res);
2899
+ * ```
2900
+ */
2901
+ stream(request, options) {
2902
+ const generator = this.processChatWithLoop(request, options?.signal);
2903
+ return new StreamResult(generator);
2904
+ }
2905
+ /**
2906
+ * Chat and collect the full response (non-streaming)
2907
+ *
2908
+ * Convenience method that calls stream().collect() for you.
2909
+ * Use this when you need the complete response before responding.
2910
+ *
2911
+ * @example
2912
+ * ```typescript
2913
+ * const { text, messages, toolCalls } = await runtime.chat(body);
2914
+ * console.log('Response:', text);
2915
+ * res.json({ response: text });
2916
+ * ```
2917
+ */
2918
+ async chat(request, options) {
2919
+ return this.stream(request, options).collect();
2920
+ }
2921
+ /**
2922
+ * Create Express-compatible handler middleware
2923
+ *
2924
+ * Returns a function that can be used directly as Express middleware.
2925
+ *
2926
+ * @example
2927
+ * ```typescript
2928
+ * // Simple usage
2929
+ * app.post('/chat', runtime.expressHandler());
2930
+ *
2931
+ * // With options
2932
+ * app.post('/chat', runtime.expressHandler({ format: 'text' }));
2933
+ * ```
2934
+ */
2935
+ expressHandler(options) {
2936
+ return async (req, res) => {
2937
+ try {
2938
+ const result = this.stream(req.body);
2939
+ if (options?.format === "text") {
2940
+ await result.pipeTextToResponse(res, { headers: options?.headers });
2941
+ } else {
2942
+ await result.pipeToResponse(res, { headers: options?.headers });
2943
+ }
2944
+ } catch (error) {
2945
+ console.error("[Runtime] Express handler error:", error);
2946
+ res.status(500).json({
2947
+ error: error instanceof Error ? error.message : "Unknown error"
2948
+ });
2949
+ }
2950
+ };
2951
+ }
2467
2952
  };
2468
2953
  function createRuntime(config) {
2469
2954
  return new Runtime(config);
@@ -2551,38 +3036,28 @@ function createNextHandler(config) {
2551
3036
  return runtime.handleRequest(request);
2552
3037
  };
2553
3038
  }
2554
- function createExpressMiddleware(config) {
3039
+ function createExpressMiddleware(config, options) {
2555
3040
  const runtime = createRuntime(config);
2556
- createHonoApp(runtime);
2557
3041
  return async (req, res) => {
2558
3042
  try {
2559
- const url = new URL(req.url, "http://localhost");
2560
- const request = new Request(url, {
2561
- method: req.method,
2562
- headers: req.headers,
2563
- body: req.method !== "GET" ? JSON.stringify(req.body) : void 0
2564
- });
2565
- const response = await runtime.handleRequest(request);
2566
- response.headers.forEach((value, key) => {
2567
- res.setHeader(key, value);
2568
- });
2569
- if (response.body) {
2570
- const reader = response.body.getReader();
2571
- const decoder = new TextDecoder();
2572
- while (true) {
2573
- const { done, value } = await reader.read();
2574
- if (done) break;
2575
- res.write(decoder.decode(value));
2576
- }
3043
+ const result = runtime.stream(req.body);
3044
+ const nodeRes = res;
3045
+ if (options?.format === "text") {
3046
+ await result.pipeTextToResponse(nodeRes, { headers: options?.headers });
3047
+ } else {
3048
+ await result.pipeToResponse(nodeRes, { headers: options?.headers });
2577
3049
  }
2578
- res.end();
2579
3050
  } catch (error) {
3051
+ console.error("[Express Middleware] Error:", error);
2580
3052
  res.status(500).json({
2581
3053
  error: error instanceof Error ? error.message : "Unknown error"
2582
3054
  });
2583
3055
  }
2584
3056
  };
2585
3057
  }
3058
+ function createExpressHandler(runtime, options) {
3059
+ return runtime.expressHandler(options);
3060
+ }
2586
3061
  function createNodeHandler(config) {
2587
3062
  const runtime = createRuntime(config);
2588
3063
  const app = createHonoApp(runtime);
@@ -3149,7 +3624,9 @@ async function executeToolCalls(toolCalls, tools, executeServerTool, waitForClie
3149
3624
  exports.DEFAULT_CAPABILITIES = DEFAULT_CAPABILITIES;
3150
3625
  exports.DEFAULT_MAX_ITERATIONS = DEFAULT_MAX_ITERATIONS;
3151
3626
  exports.Runtime = Runtime;
3627
+ exports.StreamResult = StreamResult;
3152
3628
  exports.createEventStream = createEventStream;
3629
+ exports.createExpressHandler = createExpressHandler;
3153
3630
  exports.createExpressMiddleware = createExpressMiddleware;
3154
3631
  exports.createHonoApp = createHonoApp;
3155
3632
  exports.createNextHandler = createNextHandler;
@@ -3157,11 +3634,16 @@ exports.createNodeHandler = createNodeHandler;
3157
3634
  exports.createRuntime = createRuntime;
3158
3635
  exports.createSSEHeaders = createSSEHeaders;
3159
3636
  exports.createSSEResponse = createSSEResponse;
3637
+ exports.createStreamResult = createStreamResult;
3638
+ exports.createTextStreamHeaders = createTextStreamHeaders;
3639
+ exports.createTextStreamResponse = createTextStreamResponse;
3160
3640
  exports.formatSSEData = formatSSEData;
3161
3641
  exports.formatToolsForAnthropic = formatToolsForAnthropic;
3162
3642
  exports.formatToolsForGoogle = formatToolsForGoogle;
3163
3643
  exports.formatToolsForOpenAI = formatToolsForOpenAI;
3164
3644
  exports.generateText = generateText;
3645
+ exports.pipeSSEToResponse = pipeSSEToResponse;
3646
+ exports.pipeTextToResponse = pipeTextToResponse;
3165
3647
  exports.runAgentLoop = runAgentLoop;
3166
3648
  exports.streamText = streamText;
3167
3649
  exports.tool = tool;