@diologue/local-agent 0.1.0 → 0.1.2

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/cli.mjs CHANGED
@@ -1351,6 +1351,366 @@ var translateResponse = (response, id, modelEcho) => {
1351
1351
  };
1352
1352
  };
1353
1353
 
1354
+ // src/shim/responses-translator.ts
1355
+ var flattenContent = (content) => {
1356
+ if (typeof content === "string") return content;
1357
+ return content.map((part) => {
1358
+ if (part.type === "input_text" || part.type === "output_text") {
1359
+ return part.text ?? "";
1360
+ }
1361
+ return "";
1362
+ }).join("");
1363
+ };
1364
+ var responsesToChatCompletions = (req) => {
1365
+ const messages = [];
1366
+ for (const item of req.input) {
1367
+ if ("type" in item && item.type === "function_call") {
1368
+ messages.push({
1369
+ role: "assistant",
1370
+ content: null,
1371
+ tool_calls: [
1372
+ {
1373
+ id: item.call_id,
1374
+ type: "function",
1375
+ function: { name: item.name, arguments: item.arguments }
1376
+ }
1377
+ ]
1378
+ });
1379
+ continue;
1380
+ }
1381
+ if ("type" in item && item.type === "function_call_output") {
1382
+ messages.push({
1383
+ role: "tool",
1384
+ content: item.output,
1385
+ tool_call_id: item.call_id
1386
+ });
1387
+ continue;
1388
+ }
1389
+ const role = item.role;
1390
+ const content = flattenContent(item.content);
1391
+ messages.push({ role, content });
1392
+ }
1393
+ const tools = req.tools?.map((t) => ({
1394
+ type: "function",
1395
+ function: {
1396
+ name: t.name,
1397
+ description: t.description,
1398
+ parameters: t.parameters
1399
+ }
1400
+ }));
1401
+ let toolChoice;
1402
+ if (typeof req.tool_choice === "string") {
1403
+ toolChoice = req.tool_choice;
1404
+ } else if (req.tool_choice && typeof req.tool_choice === "object") {
1405
+ toolChoice = {
1406
+ type: "function",
1407
+ function: { name: req.tool_choice.name }
1408
+ };
1409
+ }
1410
+ return {
1411
+ model: req.model,
1412
+ messages,
1413
+ tools,
1414
+ tool_choice: toolChoice,
1415
+ temperature: req.temperature,
1416
+ stream: req.stream,
1417
+ // Pass max_output_tokens through as max_tokens for downstream
1418
+ // providers that honour it. Chat Completions naming.
1419
+ max_tokens: req.max_output_tokens
1420
+ };
1421
+ };
1422
+ var randomId = (prefix) => `${prefix}_${Math.random().toString(36).slice(2, 12)}`;
1423
+ var ResponsesStreamState = class {
1424
+ responseId;
1425
+ model;
1426
+ createdAt;
1427
+ outputIndex = 0;
1428
+ /** Open message item carrying assistant text, if any. */
1429
+ text = null;
1430
+ /** Open function_call items, keyed by the broker's tool_call index
1431
+ * (the model's call position, not our output_index). */
1432
+ tools = /* @__PURE__ */ new Map();
1433
+ finished = false;
1434
+ /** Set when broker emits `complete`. We carry usage + finish_reason
1435
+ * into the final response.completed payload. */
1436
+ finalUsage;
1437
+ finalStatus = "completed";
1438
+ constructor(model, id) {
1439
+ this.responseId = id ?? randomId("resp");
1440
+ this.model = model;
1441
+ this.createdAt = Math.floor(Date.now() / 1e3);
1442
+ }
1443
+ /** Emit the response.created + response.in_progress pair. Call once
1444
+ * before any chunks. */
1445
+ start() {
1446
+ const responseObj = {
1447
+ id: this.responseId,
1448
+ object: "response",
1449
+ created_at: this.createdAt,
1450
+ status: "in_progress",
1451
+ model: this.model,
1452
+ output: [],
1453
+ usage: null
1454
+ };
1455
+ return [
1456
+ { event: "response.created", data: { type: "response.created", response: responseObj } },
1457
+ { event: "response.in_progress", data: { type: "response.in_progress", response: responseObj } }
1458
+ ];
1459
+ }
1460
+ /** Translate a single broker chunk. Some chunks emit multiple events
1461
+ * (opening a new item requires output_item.added + content_part.added
1462
+ * before the delta itself). */
1463
+ chunk(c) {
1464
+ if (this.finished) return [];
1465
+ switch (c.type) {
1466
+ case "text_delta":
1467
+ return this.handleTextDelta(c.text);
1468
+ case "tool_call_delta":
1469
+ return this.handleToolCallDelta(c);
1470
+ case "complete":
1471
+ {
1472
+ const tu = c.payload?.tokenUsage;
1473
+ if (tu) {
1474
+ const input = tu.input ?? 0;
1475
+ const output = tu.output ?? 0;
1476
+ this.finalUsage = {
1477
+ input_tokens: input,
1478
+ output_tokens: output,
1479
+ total_tokens: input + output
1480
+ };
1481
+ }
1482
+ }
1483
+ return [];
1484
+ case "error":
1485
+ this.finalStatus = "failed";
1486
+ return [];
1487
+ }
1488
+ }
1489
+ /** Close any open items + emit the terminal response.completed event. */
1490
+ finish() {
1491
+ if (this.finished) return [];
1492
+ this.finished = true;
1493
+ const out = [];
1494
+ out.push(...this.closeTextItem());
1495
+ out.push(...this.closeAllToolItems());
1496
+ const responseObj = {
1497
+ id: this.responseId,
1498
+ object: "response",
1499
+ created_at: this.createdAt,
1500
+ status: this.finalStatus,
1501
+ model: this.model,
1502
+ output: [],
1503
+ // For brevity — the AI SDK only needs status + usage at completion.
1504
+ usage: this.finalUsage ?? null
1505
+ };
1506
+ out.push({
1507
+ event: "response.completed",
1508
+ data: { type: "response.completed", response: responseObj }
1509
+ });
1510
+ return out;
1511
+ }
1512
+ // ---- internals ----
1513
+ handleTextDelta(text) {
1514
+ if (!this.text) {
1515
+ const itemId = randomId("msg");
1516
+ const outputIndex = this.outputIndex++;
1517
+ const contentIndex = 0;
1518
+ this.text = { itemId, outputIndex, contentIndex, buffer: text };
1519
+ return [
1520
+ {
1521
+ event: "response.output_item.added",
1522
+ data: {
1523
+ type: "response.output_item.added",
1524
+ output_index: outputIndex,
1525
+ item: {
1526
+ id: itemId,
1527
+ type: "message",
1528
+ status: "in_progress",
1529
+ role: "assistant",
1530
+ content: []
1531
+ }
1532
+ }
1533
+ },
1534
+ {
1535
+ event: "response.content_part.added",
1536
+ data: {
1537
+ type: "response.content_part.added",
1538
+ item_id: itemId,
1539
+ output_index: outputIndex,
1540
+ content_index: contentIndex,
1541
+ part: { type: "output_text", text: "" }
1542
+ }
1543
+ },
1544
+ {
1545
+ event: "response.output_text.delta",
1546
+ data: {
1547
+ type: "response.output_text.delta",
1548
+ item_id: itemId,
1549
+ output_index: outputIndex,
1550
+ content_index: contentIndex,
1551
+ delta: text
1552
+ }
1553
+ }
1554
+ ];
1555
+ }
1556
+ this.text.buffer += text;
1557
+ return [
1558
+ {
1559
+ event: "response.output_text.delta",
1560
+ data: {
1561
+ type: "response.output_text.delta",
1562
+ item_id: this.text.itemId,
1563
+ output_index: this.text.outputIndex,
1564
+ content_index: this.text.contentIndex,
1565
+ delta: text
1566
+ }
1567
+ }
1568
+ ];
1569
+ }
1570
+ handleToolCallDelta(c) {
1571
+ const events = [];
1572
+ events.push(...this.closeTextItem());
1573
+ let entry = this.tools.get(c.index);
1574
+ if (!entry) {
1575
+ entry = {
1576
+ itemId: randomId("fc"),
1577
+ outputIndex: this.outputIndex++,
1578
+ callId: c.id ?? randomId("call"),
1579
+ name: c.name ?? "",
1580
+ arguments: "",
1581
+ emittedAdded: false
1582
+ };
1583
+ this.tools.set(c.index, entry);
1584
+ } else {
1585
+ if (c.id) entry.callId = c.id;
1586
+ if (c.name) entry.name = c.name;
1587
+ }
1588
+ if (!entry.emittedAdded && entry.name) {
1589
+ entry.emittedAdded = true;
1590
+ events.push({
1591
+ event: "response.output_item.added",
1592
+ data: {
1593
+ type: "response.output_item.added",
1594
+ output_index: entry.outputIndex,
1595
+ item: {
1596
+ id: entry.itemId,
1597
+ type: "function_call",
1598
+ status: "in_progress",
1599
+ name: entry.name,
1600
+ call_id: entry.callId,
1601
+ arguments: ""
1602
+ }
1603
+ }
1604
+ });
1605
+ }
1606
+ if (c.argumentsDelta !== void 0 && c.argumentsDelta.length > 0) {
1607
+ entry.arguments += c.argumentsDelta;
1608
+ if (entry.emittedAdded) {
1609
+ events.push({
1610
+ event: "response.function_call_arguments.delta",
1611
+ data: {
1612
+ type: "response.function_call_arguments.delta",
1613
+ item_id: entry.itemId,
1614
+ output_index: entry.outputIndex,
1615
+ delta: c.argumentsDelta
1616
+ }
1617
+ });
1618
+ }
1619
+ }
1620
+ return events;
1621
+ }
1622
+ closeTextItem() {
1623
+ if (!this.text) return [];
1624
+ const { itemId, outputIndex, contentIndex, buffer } = this.text;
1625
+ this.text = null;
1626
+ return [
1627
+ {
1628
+ event: "response.output_text.done",
1629
+ data: {
1630
+ type: "response.output_text.done",
1631
+ item_id: itemId,
1632
+ output_index: outputIndex,
1633
+ content_index: contentIndex,
1634
+ text: buffer
1635
+ }
1636
+ },
1637
+ {
1638
+ event: "response.content_part.done",
1639
+ data: {
1640
+ type: "response.content_part.done",
1641
+ item_id: itemId,
1642
+ output_index: outputIndex,
1643
+ content_index: contentIndex,
1644
+ part: { type: "output_text", text: buffer }
1645
+ }
1646
+ },
1647
+ {
1648
+ event: "response.output_item.done",
1649
+ data: {
1650
+ type: "response.output_item.done",
1651
+ output_index: outputIndex,
1652
+ item: {
1653
+ id: itemId,
1654
+ type: "message",
1655
+ status: "completed",
1656
+ role: "assistant",
1657
+ content: [{ type: "output_text", text: buffer }]
1658
+ }
1659
+ }
1660
+ }
1661
+ ];
1662
+ }
1663
+ closeAllToolItems() {
1664
+ const out = [];
1665
+ for (const entry of this.tools.values()) {
1666
+ if (!entry.emittedAdded) {
1667
+ entry.emittedAdded = true;
1668
+ out.push({
1669
+ event: "response.output_item.added",
1670
+ data: {
1671
+ type: "response.output_item.added",
1672
+ output_index: entry.outputIndex,
1673
+ item: {
1674
+ id: entry.itemId,
1675
+ type: "function_call",
1676
+ status: "in_progress",
1677
+ name: entry.name || "unknown",
1678
+ call_id: entry.callId,
1679
+ arguments: ""
1680
+ }
1681
+ }
1682
+ });
1683
+ }
1684
+ out.push({
1685
+ event: "response.function_call_arguments.done",
1686
+ data: {
1687
+ type: "response.function_call_arguments.done",
1688
+ item_id: entry.itemId,
1689
+ output_index: entry.outputIndex,
1690
+ arguments: entry.arguments
1691
+ }
1692
+ });
1693
+ out.push({
1694
+ event: "response.output_item.done",
1695
+ data: {
1696
+ type: "response.output_item.done",
1697
+ output_index: entry.outputIndex,
1698
+ item: {
1699
+ id: entry.itemId,
1700
+ type: "function_call",
1701
+ status: "completed",
1702
+ name: entry.name || "unknown",
1703
+ call_id: entry.callId,
1704
+ arguments: entry.arguments
1705
+ }
1706
+ }
1707
+ });
1708
+ }
1709
+ this.tools.clear();
1710
+ return out;
1711
+ }
1712
+ };
1713
+
1354
1714
  // src/shim/active-broker.ts
1355
1715
  import { randomUUID as randomUUID2 } from "node:crypto";
1356
1716
  var byToken = /* @__PURE__ */ new Map();
@@ -1370,6 +1730,11 @@ var bindActiveBroker = (broker) => {
1370
1730
  var getActiveBrokerByToken = (token) => {
1371
1731
  return byToken.get(token)?.broker ?? null;
1372
1732
  };
1733
+ var getOnlyActiveBroker = () => {
1734
+ if (byToken.size !== 1) return null;
1735
+ const first = byToken.values().next().value;
1736
+ return first?.broker ?? null;
1737
+ };
1373
1738
 
1374
1739
  // src/routes/llm-shim.ts
1375
1740
  var extractBearer = (req) => {
@@ -1391,7 +1756,7 @@ var createLlmShimRouter = () => {
1391
1756
  sendOpenAIError(res, 401, "Missing Authorization bearer", "auth_error");
1392
1757
  return;
1393
1758
  }
1394
- const broker = getActiveBrokerByToken(token);
1759
+ const broker = getActiveBrokerByToken(token) ?? getOnlyActiveBroker();
1395
1760
  if (!broker) {
1396
1761
  sendOpenAIError(
1397
1762
  res,
@@ -1545,6 +1910,129 @@ var createLlmShimRouter = () => {
1545
1910
  res.json(out);
1546
1911
  }
1547
1912
  );
1913
+ router.post("/v1/responses", async (req, res) => {
1914
+ const token = extractBearer(req);
1915
+ if (!token) {
1916
+ sendOpenAIError(res, 401, "Missing Authorization bearer", "auth_error");
1917
+ return;
1918
+ }
1919
+ const broker = getActiveBrokerByToken(token);
1920
+ if (!broker) {
1921
+ sendOpenAIError(
1922
+ res,
1923
+ 401,
1924
+ "No active coding-agent stream is currently authorised on this helper.",
1925
+ "auth_error"
1926
+ );
1927
+ return;
1928
+ }
1929
+ const body = req.body;
1930
+ if (!body || typeof body !== "object" || !Array.isArray(body.input) || body.input.length === 0) {
1931
+ sendOpenAIError(res, 400, "input[] is required and must be non-empty");
1932
+ return;
1933
+ }
1934
+ const chatRequest = responsesToChatCompletions(body);
1935
+ const brokerRequest = translateRequest(chatRequest);
1936
+ const stream = body.stream === true;
1937
+ if (stream) {
1938
+ res.setHeader("Content-Type", "text/event-stream");
1939
+ res.setHeader("Cache-Control", "no-cache, no-transform");
1940
+ res.setHeader("Connection", "keep-alive");
1941
+ res.flushHeaders?.();
1942
+ const state = new ResponsesStreamState(body.model);
1943
+ const writeEvent2 = (ev) => {
1944
+ res.write(`event: ${ev.event}
1945
+ `);
1946
+ res.write(`data: ${JSON.stringify(ev.data)}
1947
+
1948
+ `);
1949
+ };
1950
+ for (const ev of state.start()) writeEvent2(ev);
1951
+ const observer = (chunk) => {
1952
+ for (const ev of state.chunk(chunk)) writeEvent2(ev);
1953
+ };
1954
+ try {
1955
+ await broker.request(brokerRequest, observer);
1956
+ } catch (err) {
1957
+ writeEvent2({
1958
+ event: "response.failed",
1959
+ data: {
1960
+ type: "response.failed",
1961
+ response: {
1962
+ id: `resp_${Math.random().toString(36).slice(2, 12)}`,
1963
+ object: "response",
1964
+ status: "failed",
1965
+ model: body.model,
1966
+ error: {
1967
+ code: "broker_error",
1968
+ message: err instanceof Error ? err.message : "Broker call failed"
1969
+ }
1970
+ }
1971
+ }
1972
+ });
1973
+ res.write("data: [DONE]\n\n");
1974
+ res.end();
1975
+ return;
1976
+ }
1977
+ for (const ev of state.finish()) writeEvent2(ev);
1978
+ res.write("data: [DONE]\n\n");
1979
+ res.end();
1980
+ return;
1981
+ }
1982
+ let brokerResponse;
1983
+ try {
1984
+ brokerResponse = await broker.request(brokerRequest);
1985
+ } catch (err) {
1986
+ sendOpenAIError(
1987
+ res,
1988
+ 502,
1989
+ err instanceof Error ? err.message : "Broker call failed",
1990
+ "api_error"
1991
+ );
1992
+ return;
1993
+ }
1994
+ if (brokerResponse.error) {
1995
+ sendOpenAIError(res, 502, brokerResponse.error, "api_error");
1996
+ return;
1997
+ }
1998
+ const completionId = `resp_${Math.random().toString(36).slice(2, 12)}`;
1999
+ const chatResponse = translateResponse(
2000
+ brokerResponse,
2001
+ completionId,
2002
+ body.model
2003
+ );
2004
+ const text = chatResponse.choices[0]?.message?.content ?? "";
2005
+ const toolCalls = chatResponse.choices[0]?.message?.tool_calls ?? [];
2006
+ const output = [];
2007
+ if (text) {
2008
+ output.push({
2009
+ id: `msg_${Math.random().toString(36).slice(2, 12)}`,
2010
+ type: "message",
2011
+ status: "completed",
2012
+ role: "assistant",
2013
+ content: [{ type: "output_text", text }]
2014
+ });
2015
+ }
2016
+ for (const tc of toolCalls) {
2017
+ output.push({
2018
+ id: `fc_${Math.random().toString(36).slice(2, 12)}`,
2019
+ type: "function_call",
2020
+ status: "completed",
2021
+ name: tc.function.name,
2022
+ call_id: tc.id,
2023
+ arguments: tc.function.arguments
2024
+ });
2025
+ }
2026
+ res.json({
2027
+ id: completionId,
2028
+ object: "response",
2029
+ created_at: Math.floor(Date.now() / 1e3),
2030
+ status: "completed",
2031
+ model: body.model,
2032
+ output,
2033
+ usage: chatResponse.usage
2034
+ });
2035
+ });
1548
2036
  return router;
1549
2037
  };
1550
2038