





















@@ -1654,6 +1654,232 @@ describe("openai transport stream", () => {
16541654expect(params.tools?.[0]?.function?.strict).toBe(false);
16551655});
165616561657+describe("Gemini thought_signature round-trip on OpenAI-compatible completions", () => {
1658+const geminiModel = {
1659+id: "gemini-3-flash-preview",
1660+name: "Gemini 3 Flash Preview",
1661+api: "openai-completions",
1662+provider: "google",
1663+baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
1664+reasoning: true,
1665+input: ["text"],
1666+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1667+contextWindow: 1_000_000,
1668+maxTokens: 8192,
1669+} satisfies Model<"openai-completions">;
1670+1671+function makeAssistantOutput(model: Model<"openai-completions">) {
1672+return {
1673+role: "assistant" as const,
1674+content: [] as Array<Record<string, unknown>>,
1675+api: model.api,
1676+provider: model.provider,
1677+model: model.id,
1678+usage: {
1679+input: 0,
1680+output: 0,
1681+cacheRead: 0,
1682+cacheWrite: 0,
1683+totalTokens: 0,
1684+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
1685+},
1686+stopReason: "stop",
1687+timestamp: Date.now(),
1688+};
1689+}
1690+1691+it("captures thought_signature from streamed Google tool_calls", async () => {
1692+const output = makeAssistantOutput(geminiModel);
1693+const chunks = [
1694+{
1695+id: "chatcmpl-gemini",
1696+object: "chat.completion.chunk" as const,
1697+created: 1,
1698+model: geminiModel.id,
1699+choices: [
1700+{
1701+index: 0,
1702+delta: {
1703+tool_calls: [
1704+{
1705+index: 0,
1706+id: "call_abc",
1707+type: "function",
1708+function: { name: "echo_value", arguments: "" },
1709+extra_content: { google: { thought_signature: "SIG-OPAQUE-ABC==" } },
1710+},
1711+],
1712+},
1713+logprobs: null,
1714+finish_reason: null,
1715+},
1716+],
1717+},
1718+{
1719+id: "chatcmpl-gemini",
1720+object: "chat.completion.chunk" as const,
1721+created: 1,
1722+model: geminiModel.id,
1723+choices: [
1724+{
1725+index: 0,
1726+delta: {
1727+tool_calls: [{ index: 0, function: { arguments: '{"value":"repro"}' } }],
1728+},
1729+logprobs: null,
1730+finish_reason: "tool_calls" as const,
1731+},
1732+],
1733+},
1734+] as const;
1735+async function* mockStream() {
1736+for (const chunk of chunks) {
1737+yield chunk as never;
1738+}
1739+}
1740+1741+await __testing.processOpenAICompletionsStream(mockStream(), output, geminiModel, {
1742+push() {},
1743+});
1744+1745+expect(output.content[0]).toMatchObject({
1746+type: "toolCall",
1747+id: "call_abc",
1748+name: "echo_value",
1749+arguments: { value: "repro" },
1750+thoughtSignature: "SIG-OPAQUE-ABC==",
1751+});
1752+});
1753+1754+it("re-emits captured thought_signature for same Google route tool-call replay", () => {
1755+const params = buildOpenAICompletionsParams(
1756+geminiModel,
1757+{
1758+messages: [
1759+{ role: "user", content: "echo" },
1760+{
1761+role: "assistant",
1762+api: geminiModel.api,
1763+provider: geminiModel.provider,
1764+model: geminiModel.id,
1765+usage: {
1766+input: 0,
1767+output: 0,
1768+cacheRead: 0,
1769+cacheWrite: 0,
1770+totalTokens: 0,
1771+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
1772+},
1773+stopReason: "toolUse",
1774+timestamp: 1,
1775+content: [
1776+{
1777+type: "toolCall",
1778+id: "call_abc",
1779+name: "echo_value",
1780+arguments: { value: "repro" },
1781+thoughtSignature: "SIG-OPAQUE-ABC==",
1782+},
1783+],
1784+},
1785+{
1786+role: "toolResult",
1787+toolCallId: "call_abc",
1788+toolName: "echo_value",
1789+content: [{ type: "text", text: "ok" }],
1790+isError: false,
1791+},
1792+],
1793+tools: [],
1794+} as never,
1795+undefined,
1796+) as { messages: Array<Record<string, unknown>> };
1797+1798+const assistant = params.messages.find((message) => message.role === "assistant") as
1799+| { tool_calls?: Array<{ extra_content?: { google?: { thought_signature?: string } } }> }
1800+| undefined;
1801+expect(assistant?.tool_calls?.[0]?.extra_content?.google?.thought_signature).toBe(
1802+"SIG-OPAQUE-ABC==",
1803+);
1804+});
1805+1806+it("does not replay thought_signature across a different API surface", () => {
1807+const params = buildOpenAICompletionsParams(
1808+geminiModel,
1809+{
1810+messages: [
1811+{
1812+role: "assistant",
1813+api: "google-generative-ai",
1814+provider: geminiModel.provider,
1815+model: geminiModel.id,
1816+usage: {
1817+input: 0,
1818+output: 0,
1819+cacheRead: 0,
1820+cacheWrite: 0,
1821+totalTokens: 0,
1822+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
1823+},
1824+stopReason: "toolUse",
1825+timestamp: 1,
1826+content: [
1827+{
1828+type: "toolCall",
1829+id: "call_abc",
1830+name: "echo_value",
1831+arguments: { value: "repro" },
1832+thoughtSignature: "SIG-OPAQUE-ABC==",
1833+},
1834+],
1835+},
1836+],
1837+tools: [],
1838+} as never,
1839+undefined,
1840+) as { messages: Array<Record<string, unknown>> };
1841+1842+const assistant = params.messages.find((message) => message.role === "assistant") as
1843+| { tool_calls?: Array<{ extra_content?: unknown }> }
1844+| undefined;
1845+expect(assistant?.tool_calls?.[0]?.extra_content).toBeUndefined();
1846+});
1847+1848+it("does not emit extra_content when no thought_signature was captured", () => {
1849+const params = buildOpenAICompletionsParams(
1850+geminiModel,
1851+{
1852+messages: [
1853+{
1854+role: "assistant",
1855+api: geminiModel.api,
1856+provider: geminiModel.provider,
1857+model: geminiModel.id,
1858+usage: {
1859+input: 0,
1860+output: 0,
1861+cacheRead: 0,
1862+cacheWrite: 0,
1863+totalTokens: 0,
1864+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
1865+},
1866+stopReason: "toolUse",
1867+timestamp: 1,
1868+content: [{ type: "toolCall", id: "call_abc", name: "echo_value", arguments: {} }],
1869+},
1870+],
1871+tools: [],
1872+} as never,
1873+undefined,
1874+) as { messages: Array<Record<string, unknown>> };
1875+1876+const assistant = params.messages.find((message) => message.role === "assistant") as
1877+| { tool_calls?: Array<{ extra_content?: unknown }> }
1878+| undefined;
1879+expect(assistant?.tool_calls?.[0]?.extra_content).toBeUndefined();
1880+});
1881+});
1882+16571883it("uses Mistral compat defaults for direct Mistral completions providers", () => {
16581884const params = buildOpenAICompletionsParams(
16591885{
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。