@polpo-ai/server 0.10.7 → 0.10.9

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,148 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ const { generateTextMock } = vi.hoisted(() => ({
3
+ generateTextMock: vi.fn(),
4
+ }));
5
+ vi.mock("ai", () => ({
6
+ generateText: generateTextMock,
7
+ streamText: vi.fn(),
8
+ jsonSchema: (schema) => schema,
9
+ }));
10
+ import { completionRoutes } from "./completions.js";
11
+ describe("completionRoutes provider-executed tools", () => {
12
+ function makeDeps() {
13
+ return {
14
+ getAgents: async () => [{
15
+ name: "researcher",
16
+ model: "test",
17
+ assignedLoops: ["research-loop"],
18
+ defaultLoop: "research-loop",
19
+ allowedTools: ["search_web"],
20
+ }],
21
+ getConfig: () => ({}),
22
+ getMemoryStore: () => null,
23
+ getSessionStore: () => null,
24
+ getStore: () => null,
25
+ emit: () => { },
26
+ buildAgentPrompt: () => "You are a researcher.",
27
+ resolveAgentModel: async () => ({
28
+ model: {
29
+ id: "test",
30
+ provider: "test",
31
+ aiModel: "test-model",
32
+ contextWindow: 100_000,
33
+ maxTokens: 1024,
34
+ },
35
+ providerOptions: undefined,
36
+ }),
37
+ resolveAgentTools: async () => ({
38
+ tools: [],
39
+ extraAiTools: {
40
+ search_web: { type: "provider-defined", id: "gateway.perplexity_search" },
41
+ },
42
+ executor: async (name) => {
43
+ throw new Error(`provider tool "${name}" should not be executed locally`);
44
+ },
45
+ }),
46
+ getProjectLoop: async (name) => ({
47
+ name,
48
+ context: "shared",
49
+ start: "research",
50
+ steps: {
51
+ research: {
52
+ type: "agent",
53
+ systemPrompt: "Search the web, then summarize the result.",
54
+ tools: ["search_web"],
55
+ maxTurns: 3,
56
+ next: "end",
57
+ },
58
+ },
59
+ }),
60
+ };
61
+ }
62
+ it("feeds provider-executed tool results back through AI SDK responseMessages", async () => {
63
+ let secondTurnMessages;
64
+ const searchOutput = {
65
+ results: [{ title: "Polpo", url: "https://polpo.sh", snippet: "Agent backend" }],
66
+ id: "search_1",
67
+ };
68
+ generateTextMock
69
+ .mockResolvedValueOnce({
70
+ text: "",
71
+ usage: { inputTokens: 10, outputTokens: 2, totalTokens: 12 },
72
+ providerMetadata: undefined,
73
+ toolCalls: [{
74
+ toolCallId: "call_search",
75
+ toolName: "search_web",
76
+ input: { query: "Polpo agent backend" },
77
+ providerExecuted: true,
78
+ }],
79
+ toolResults: [{
80
+ type: "tool-result",
81
+ toolCallId: "call_search",
82
+ toolName: "search_web",
83
+ output: searchOutput,
84
+ providerExecuted: true,
85
+ }],
86
+ responseMessages: [{
87
+ role: "assistant",
88
+ content: [
89
+ {
90
+ type: "tool-call",
91
+ toolCallId: "call_search",
92
+ toolName: "search_web",
93
+ input: { query: "Polpo agent backend" },
94
+ providerExecuted: true,
95
+ },
96
+ {
97
+ type: "tool-result",
98
+ toolCallId: "call_search",
99
+ toolName: "search_web",
100
+ output: { type: "json", value: searchOutput },
101
+ providerExecuted: true,
102
+ },
103
+ ],
104
+ }],
105
+ })
106
+ .mockImplementationOnce(async (args) => {
107
+ secondTurnMessages = args.messages;
108
+ return {
109
+ text: "Polpo is an agent backend.",
110
+ usage: { inputTokens: 20, outputTokens: 5, totalTokens: 25 },
111
+ providerMetadata: undefined,
112
+ toolCalls: [],
113
+ toolResults: [],
114
+ responseMessages: [{
115
+ role: "assistant",
116
+ content: "Polpo is an agent backend.",
117
+ }],
118
+ };
119
+ });
120
+ const app = completionRoutes(() => makeDeps());
121
+ const res = await app.request("/", {
122
+ method: "POST",
123
+ headers: { "content-type": "application/json" },
124
+ body: JSON.stringify({
125
+ agent: "researcher",
126
+ messages: [{ role: "user", content: "Research Polpo" }],
127
+ }),
128
+ });
129
+ expect(res.status).toBe(200);
130
+ const json = await res.json();
131
+ expect(json.choices[0].message.content).toBe("Polpo is an agent backend.");
132
+ expect(generateTextMock).toHaveBeenCalledTimes(2);
133
+ expect(secondTurnMessages).toEqual(expect.arrayContaining([
134
+ expect.objectContaining({
135
+ role: "assistant",
136
+ content: expect.arrayContaining([
137
+ expect.objectContaining({
138
+ type: "tool-result",
139
+ toolName: "search_web",
140
+ providerExecuted: true,
141
+ }),
142
+ ]),
143
+ }),
144
+ ]));
145
+ expect(secondTurnMessages?.filter((message) => message.role === "tool" && JSON.stringify(message).includes("search_web"))).toEqual([]);
146
+ });
147
+ });
148
+ //# sourceMappingURL=completions-provider-tools.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completions-provider-tools.test.js","sourceRoot":"","sources":["../../src/routes/completions-provider-tools.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"completions.d.ts","sourceRoot":"","sources":["../../src/routes/completions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAGhE,OAAO,EAcL,KAAK,aAAa,EAGlB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EAEvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAwC,KAAK,aAAa,EAA2B,MAAM,IAAI,CAAC;AAwhBvG;;;GAGG;AACH,UAAU,iBAAiB;IACzB,mFAAmF;IACnF,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAChC,SAAS,EAAE,MAAM,GAAG,CAAC;IACrB,cAAc,EAAE,MAAM,GAAG,CAAC;IAC1B,eAAe,EAAE,MAAM,GAAG,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,CAAC;IACpB,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IACzC,wIAAwI;IACxI,iBAAiB,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE,iBAAiB,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;QAC3E,KAAK,EAAE,iBAAiB,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KACvC,CAAC,CAAC;IACH,yDAAyD;IACzD,gBAAgB,EAAE,CAAC,WAAW,EAAE,GAAG,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjE;;;;;;;;;gEAS4D;IAC5D,iBAAiB,EAAE,CAAC,WAAW,EAAE,GAAG,KAAK,OAAO,CAAC;QAC/C,KAAK,EAAE,GAAG,EAAE,CAAC;QACb,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;QAC3E,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QAC9B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KACpC,CAAC,CAAC;IACH,uHAAuH;IACvH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACrE,8DAA8D;IAC9D,eAAe,CAAC,EAAE,MAAM,YAAY,GAAG,SAAS,CAAC;IACjD,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,aAAa,GAAG,SAAS,CAAC;IACnD,4KAA4K;IAC5K,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE;QAC5B,KAAK,EAAE;YAAE,WAAW,CAAC,EAAE,MAAM,CAAC;YAAC,YAAY,CAAC,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC7E,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB;mFAC2E;QAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC5C,KAAK,IAAI,CAAC;IACX,0EAA0E;IAC1E,0BAA0B,CAAC,EAAE,MAAM,OAAO,CAAC;QACzC,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,iBAAiB,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACtC,KAAK,EAAE,GAAG,EAAE,CAAC;QACb,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;QAC3E,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;KAC1C,CAAC,CAAC;CACJ;AA+hBD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,aAAa,CAAC,CAuCzB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,mBAAmB,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,WAAW,CA2jCpG"}
1
+ {"version":3,"file":"completions.d.ts","sourceRoot":"","sources":["../../src/routes/completions.ts"],"names":[],"mappings":"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"}
@@ -61,6 +61,55 @@ function redactVaultToolCalls(toolCalls) {
61
61
  return { ...tc, arguments: args };
62
62
  });
63
63
  }
64
+ async function appendModelResponseMessages(messages, result, turnText, toolCalls) {
65
+ try {
66
+ const responseMessages = await result.responseMessages;
67
+ if (Array.isArray(responseMessages) && responseMessages.length > 0) {
68
+ messages.push(...responseMessages);
69
+ return;
70
+ }
71
+ }
72
+ catch {
73
+ // Older/partial AI SDK results can still be represented manually below.
74
+ }
75
+ const assistantContent = [];
76
+ if (turnText)
77
+ assistantContent.push({ type: "text", text: turnText });
78
+ for (const tc of toolCalls) {
79
+ assistantContent.push({
80
+ type: "tool-call",
81
+ toolCallId: tc.toolCallId,
82
+ toolName: tc.toolName,
83
+ input: tc.input,
84
+ });
85
+ }
86
+ messages.push({
87
+ role: "assistant",
88
+ content: assistantContent.length === 1 && assistantContent[0].type === "text"
89
+ ? turnText
90
+ : assistantContent,
91
+ });
92
+ }
93
+ function indexToolResultsByCallId(toolResults) {
94
+ const indexed = new Map();
95
+ for (const result of toolResults ?? []) {
96
+ if (result?.toolCallId)
97
+ indexed.set(result.toolCallId, result);
98
+ }
99
+ return indexed;
100
+ }
101
+ function recordProviderToolCall(toolCallsAccum, call, toolResults) {
102
+ const toolResult = toolResults.get(call.toolCallId);
103
+ const output = toolResult?.output ?? toolResult?.result ?? toolResult?.error;
104
+ toolCallsAccum.push({
105
+ id: call.toolCallId,
106
+ name: call.toolName,
107
+ arguments: call.input,
108
+ result: output === undefined ? undefined : typeof output === "string" ? output : JSON.stringify(output),
109
+ state: toolResult?.type === "tool-error" || toolResult?.error ? "error" : "completed",
110
+ providerExecuted: true,
111
+ });
112
+ }
64
113
  // ── Zod Schemas ────────────────────────────────────────────────────────
65
114
  /** OpenAI-compatible content part (text, image_url, or file reference). */
66
115
  const contentPartSchema = z.discriminatedUnion("type", [
@@ -651,44 +700,15 @@ async function runAgentStepCompletion(options) {
651
700
  lastProviderMetadata = genResult.providerMetadata;
652
701
  }
653
702
  catch { /* best effort */ }
654
- const assistantContent = [];
655
- if (turnText)
656
- assistantContent.push({ type: "text", text: turnText });
657
- for (const tc of genResult.toolCalls) {
658
- assistantContent.push({
659
- type: "tool-call",
660
- toolCallId: tc.toolCallId,
661
- toolName: tc.toolName,
662
- input: tc.input,
663
- });
664
- }
665
- messages.push({
666
- role: "assistant",
667
- content: assistantContent.length === 1 && assistantContent[0].type === "text"
668
- ? turnText
669
- : assistantContent,
670
- });
703
+ await appendModelResponseMessages(messages, genResult, turnText, genResult.toolCalls);
671
704
  finalText += turnText;
672
705
  if (genResult.toolCalls.length === 0)
673
706
  break;
674
- const providerToolResults = new Map();
675
- for (const tr of genResult.toolResults ?? []) {
676
- providerToolResults.set(tr.toolCallId, tr);
677
- }
707
+ const providerToolResults = indexToolResultsByCallId(genResult.toolResults);
678
708
  for (const call of genResult.toolCalls) {
679
709
  const callArgs = call.input;
680
710
  if (providerToolNames.has(call.toolName)) {
681
- const tr = providerToolResults.get(call.toolCallId);
682
- const value = tr?.output ?? tr?.result ?? null;
683
- messages.push({
684
- role: "tool",
685
- content: [{
686
- type: "tool-result",
687
- toolCallId: call.toolCallId,
688
- toolName: call.toolName,
689
- output: { type: "json", value },
690
- }],
691
- });
711
+ recordProviderToolCall(toolCallsAccum, call, providerToolResults);
692
712
  continue;
693
713
  }
694
714
  const result = await resolvedTools.executor(call.toolName, callArgs);
@@ -1484,26 +1504,7 @@ export function completionRoutes(getDeps, apiKeys) {
1484
1504
  lastProviderMetadata = (await result.providerMetadata);
1485
1505
  }
1486
1506
  catch { /* best effort */ }
1487
- // Push assistant response message into conversation history
1488
- // AI SDK format: assistant message with text + tool calls
1489
- const assistantContent = [];
1490
- if (turnText) {
1491
- assistantContent.push({ type: "text", text: turnText });
1492
- }
1493
- for (const tc of toolCalls) {
1494
- assistantContent.push({
1495
- type: "tool-call",
1496
- toolCallId: tc.toolCallId,
1497
- toolName: tc.toolName,
1498
- input: tc.input,
1499
- });
1500
- }
1501
- messages.push({
1502
- role: "assistant",
1503
- content: assistantContent.length === 1 && assistantContent[0].type === "text"
1504
- ? turnText
1505
- : assistantContent,
1506
- });
1507
+ await appendModelResponseMessages(messages, result, turnText, toolCalls);
1507
1508
  finalText += turnText;
1508
1509
  if (toolCalls.length === 0)
1509
1510
  break;
@@ -1630,16 +1631,14 @@ export function completionRoutes(getDeps, apiKeys) {
1630
1631
  await stream.writeSSE({ data: "[DONE]" });
1631
1632
  return; // finally block will persist whatever finalText we have
1632
1633
  }
1633
- // Provider tools (extraAiTools) are executed by the SDK / gateway
1634
- // inside generateText itself — their results are already on
1635
- // `result.toolResults`. Skip them in our manual dispatcher; just
1636
- // forward the result message into history for the next turn.
1634
+ // Provider tools (extraAiTools) are executed by the SDK / gateway.
1635
+ // Their tool results are already preserved in responseMessages,
1636
+ // so only record them for observability and skip local dispatch.
1637
1637
  const providerToolNames = new Set(Object.keys(extraAiTools ?? {}));
1638
- const providerToolResults = new Map();
1638
+ let providerToolResults = new Map();
1639
1639
  try {
1640
1640
  const settled = (await result.toolResults);
1641
- for (const tr of settled ?? [])
1642
- providerToolResults.set(tr.toolCallId, tr);
1641
+ providerToolResults = indexToolResultsByCallId(settled);
1643
1642
  }
1644
1643
  catch { /* best effort */ }
1645
1644
  for (const call of toolCalls) {
@@ -1648,17 +1647,7 @@ export function completionRoutes(getDeps, apiKeys) {
1648
1647
  break;
1649
1648
  const callArgs = call.input;
1650
1649
  if (providerToolNames.has(call.toolName)) {
1651
- const tr = providerToolResults.get(call.toolCallId);
1652
- const value = tr?.output ?? tr?.result ?? null;
1653
- messages.push({
1654
- role: "tool",
1655
- content: [{
1656
- type: "tool-result",
1657
- toolCallId: call.toolCallId,
1658
- toolName: call.toolName,
1659
- output: { type: "json", value },
1660
- }],
1661
- });
1650
+ recordProviderToolCall(toolCallsAccum, call, providerToolResults);
1662
1651
  continue;
1663
1652
  }
1664
1653
  // Notify client that a tool is being called
@@ -1812,25 +1801,7 @@ export function completionRoutes(getDeps, apiKeys) {
1812
1801
  lastProviderMetadata = genResult.providerMetadata;
1813
1802
  }
1814
1803
  catch { /* best effort */ }
1815
- // Push assistant response message into conversation history
1816
- const assistantContent = [];
1817
- if (turnText) {
1818
- assistantContent.push({ type: "text", text: turnText });
1819
- }
1820
- for (const tc of genResult.toolCalls) {
1821
- assistantContent.push({
1822
- type: "tool-call",
1823
- toolCallId: tc.toolCallId,
1824
- toolName: tc.toolName,
1825
- input: tc.input,
1826
- });
1827
- }
1828
- messages.push({
1829
- role: "assistant",
1830
- content: assistantContent.length === 1 && assistantContent[0].type === "text"
1831
- ? turnText
1832
- : assistantContent,
1833
- });
1804
+ await appendModelResponseMessages(messages, genResult, turnText, genResult.toolCalls);
1834
1805
  finalText += turnText;
1835
1806
  const toolCalls = genResult.toolCalls;
1836
1807
  if (toolCalls.length === 0)
@@ -2002,28 +1973,15 @@ export function completionRoutes(getDeps, apiKeys) {
2002
1973
  }
2003
1974
  // Note: finally block persists finalText + toolCallsAccum
2004
1975
  }
2005
- // Provider tools (extraAiTools) are executed by the SDK / gateway
2006
- // inside generateText itself — their results are already on
2007
- // `genResult.toolResults`. Skip them in the manual dispatcher.
1976
+ // Provider tools (extraAiTools) are executed by the SDK / gateway.
1977
+ // Their tool results are already preserved in responseMessages,
1978
+ // so only record them for observability and skip local dispatch.
2008
1979
  const providerToolNames = new Set(Object.keys(extraAiTools ?? {}));
2009
- const providerToolResults = new Map();
2010
- for (const tr of genResult.toolResults ?? []) {
2011
- providerToolResults.set(tr.toolCallId, tr);
2012
- }
1980
+ const providerToolResults = indexToolResultsByCallId(genResult.toolResults);
2013
1981
  for (const call of toolCalls) {
2014
1982
  const callArgs = call.input;
2015
1983
  if (providerToolNames.has(call.toolName)) {
2016
- const tr = providerToolResults.get(call.toolCallId);
2017
- const value = tr?.output ?? tr?.result ?? null;
2018
- messages.push({
2019
- role: "tool",
2020
- content: [{
2021
- type: "tool-result",
2022
- toolCallId: call.toolCallId,
2023
- toolName: call.toolName,
2024
- output: { type: "json", value },
2025
- }],
2026
- });
1984
+ recordProviderToolCall(toolCallsAccum, call, providerToolResults);
2027
1985
  continue;
2028
1986
  }
2029
1987
  const result = await effectiveToolExecutor(call.toolName, callArgs);