@f5-sales-demo/pi-ai 21.11.4 → 21.11.6
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/package.json +2 -2
- package/src/providers/anthropic.ts +100 -10
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/pi-ai",
|
|
4
|
-
"version": "21.11.
|
|
4
|
+
"version": "21.11.6",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@anthropic-ai/sdk": "^0.115",
|
|
47
47
|
"@aws-sdk/client-bedrock-runtime": "^3",
|
|
48
48
|
"@bufbuild/protobuf": "^2.11",
|
|
49
|
-
"@f5-sales-demo/pi-utils": "21.11.
|
|
49
|
+
"@f5-sales-demo/pi-utils": "21.11.6",
|
|
50
50
|
"@google/genai": "^2.15",
|
|
51
51
|
"@sinclair/typebox": "^0.34",
|
|
52
52
|
"@smithy/node-http-handler": "^4.4",
|
|
@@ -40,6 +40,7 @@ import { finalizeErrorMessage, type RawHttpRequestDump, rewriteCopilotAuthError
|
|
|
40
40
|
import { createFirstEventWatchdog, getStreamFirstEventTimeoutMs, markFirstStreamEvent } from "../utils/idle-iterator";
|
|
41
41
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
42
42
|
import { parseGitHubCopilotApiKey } from "../utils/oauth/github-copilot";
|
|
43
|
+
import { adaptSchemaForStrict } from "../utils/schema";
|
|
43
44
|
import {
|
|
44
45
|
buildCopilotDynamicHeaders,
|
|
45
46
|
hasCopilotVisionInput,
|
|
@@ -741,7 +742,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
741
742
|
| ThinkingContent
|
|
742
743
|
| RedactedThinkingContent
|
|
743
744
|
| TextContent
|
|
744
|
-
| (ToolCall & { partialJson: string })
|
|
745
|
+
| (ToolCall & { partialJson: string; hasJsonDeltas: boolean })
|
|
745
746
|
) & { index: number };
|
|
746
747
|
const blocks = output.content as Block[];
|
|
747
748
|
const firstEventTimeoutMs = options?.streamFirstEventTimeoutMs ?? getStreamFirstEventTimeoutMs();
|
|
@@ -861,6 +862,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
861
862
|
: event.content_block.name,
|
|
862
863
|
arguments: (event.content_block.input as Record<string, unknown>) ?? {},
|
|
863
864
|
partialJson: "",
|
|
865
|
+
hasJsonDeltas: false,
|
|
864
866
|
index: event.index,
|
|
865
867
|
};
|
|
866
868
|
output.content.push(block);
|
|
@@ -929,6 +931,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
929
931
|
const index = blocks.findIndex(b => b.index === event.index);
|
|
930
932
|
const block = blocks[index];
|
|
931
933
|
if (block && block.type === "toolCall") {
|
|
934
|
+
block.hasJsonDeltas = true;
|
|
932
935
|
block.partialJson += event.delta.partial_json;
|
|
933
936
|
block.arguments = parseStreamingJson(block.partialJson);
|
|
934
937
|
stream.push({
|
|
@@ -996,8 +999,18 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
996
999
|
partial: output,
|
|
997
1000
|
});
|
|
998
1001
|
} else if (block.type === "toolCall") {
|
|
999
|
-
|
|
1002
|
+
if (block.hasJsonDeltas) {
|
|
1003
|
+
try {
|
|
1004
|
+
block.arguments = JSON.parse(block.partialJson) as Record<string, unknown>;
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
throw new Error(
|
|
1007
|
+
`Anthropic returned malformed completed tool input JSON for "${block.name}"`,
|
|
1008
|
+
{ cause: error },
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1000
1012
|
delete (block as { partialJson?: string }).partialJson;
|
|
1013
|
+
delete (block as { hasJsonDeltas?: boolean }).hasJsonDeltas;
|
|
1001
1014
|
stream.push({
|
|
1002
1015
|
type: "toolcall_end",
|
|
1003
1016
|
contentIndex: index,
|
|
@@ -1095,6 +1108,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
1095
1108
|
for (const block of output.content) {
|
|
1096
1109
|
delete (block as { index?: number }).index;
|
|
1097
1110
|
delete (block as { partialJson?: string }).partialJson;
|
|
1111
|
+
delete (block as { hasJsonDeltas?: boolean }).hasJsonDeltas;
|
|
1098
1112
|
}
|
|
1099
1113
|
const firstEventTimeoutError = activeAbortTracker.getLocalAbortReason();
|
|
1100
1114
|
output.stopReason = activeAbortTracker.wasCallerAbort() ? "aborted" : "error";
|
|
@@ -1533,7 +1547,15 @@ function buildParams(
|
|
|
1533
1547
|
}
|
|
1534
1548
|
|
|
1535
1549
|
if (context.tools) {
|
|
1536
|
-
|
|
1550
|
+
const toolChoice = options?.toolChoice;
|
|
1551
|
+
const forcedToolName =
|
|
1552
|
+
model.provider === "anthropic" &&
|
|
1553
|
+
supportsAnthropicStrictToolUse(model.id) &&
|
|
1554
|
+
typeof toolChoice === "object" &&
|
|
1555
|
+
toolChoice.type === "tool"
|
|
1556
|
+
? toolChoice.name
|
|
1557
|
+
: undefined;
|
|
1558
|
+
params.tools = convertTools(context.tools, isOAuthToken, forcedToolName);
|
|
1537
1559
|
}
|
|
1538
1560
|
|
|
1539
1561
|
if (options?.thinkingEnabled && model.reasoning) {
|
|
@@ -1761,20 +1783,88 @@ export function convertAnthropicMessages(
|
|
|
1761
1783
|
return params;
|
|
1762
1784
|
}
|
|
1763
1785
|
|
|
1764
|
-
|
|
1786
|
+
const ANTHROPIC_STRICT_TOOL_MODEL_IDS = new Set([
|
|
1787
|
+
"claude-fable-5-1",
|
|
1788
|
+
"claude-mythos-5-1",
|
|
1789
|
+
"claude-fable-5",
|
|
1790
|
+
"claude-mythos-5",
|
|
1791
|
+
"claude-mythos-preview",
|
|
1792
|
+
"claude-opus-5",
|
|
1793
|
+
"claude-opus-4-8",
|
|
1794
|
+
"claude-opus-4-7",
|
|
1795
|
+
"claude-opus-4-6",
|
|
1796
|
+
"claude-sonnet-5",
|
|
1797
|
+
"claude-sonnet-4-6",
|
|
1798
|
+
"claude-sonnet-4-5",
|
|
1799
|
+
"claude-sonnet-4-5-20250929",
|
|
1800
|
+
"claude-opus-4-5",
|
|
1801
|
+
"claude-opus-4-5-20251101",
|
|
1802
|
+
"claude-haiku-4-5",
|
|
1803
|
+
"claude-haiku-4-5-20251001",
|
|
1804
|
+
]);
|
|
1805
|
+
|
|
1806
|
+
function supportsAnthropicStrictToolUse(modelId: string): boolean {
|
|
1807
|
+
return ANTHROPIC_STRICT_TOOL_MODEL_IDS.has(modelId);
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
function countAnthropicStrictSchemaParameters(schema: Record<string, unknown>): {
|
|
1811
|
+
optional: number;
|
|
1812
|
+
unions: number;
|
|
1813
|
+
} {
|
|
1814
|
+
const counts = { optional: 0, unions: 0 };
|
|
1815
|
+
const seen = new WeakSet<object>();
|
|
1816
|
+
const visit = (node: unknown, isParameter: boolean): void => {
|
|
1817
|
+
if (node === null || typeof node !== "object" || seen.has(node)) return;
|
|
1818
|
+
seen.add(node);
|
|
1819
|
+
if (Array.isArray(node)) {
|
|
1820
|
+
for (const child of node) visit(child, false);
|
|
1821
|
+
seen.delete(node);
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
const record = node as Record<string, unknown>;
|
|
1825
|
+
if (isParameter && (Array.isArray(record.anyOf) || Array.isArray(record.type))) counts.unions++;
|
|
1826
|
+
if (record.type === "object" && record.properties && typeof record.properties === "object") {
|
|
1827
|
+
const required = new Set(Array.isArray(record.required) ? record.required : []);
|
|
1828
|
+
for (const [name, child] of Object.entries(record.properties as Record<string, unknown>)) {
|
|
1829
|
+
if (!required.has(name)) counts.optional++;
|
|
1830
|
+
visit(child, true);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
for (const [key, child] of Object.entries(record)) {
|
|
1834
|
+
if (key !== "properties") visit(child, false);
|
|
1835
|
+
}
|
|
1836
|
+
seen.delete(node);
|
|
1837
|
+
};
|
|
1838
|
+
visit(schema, false);
|
|
1839
|
+
return counts;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
function isWithinAnthropicStrictSchemaLimits(schema: Record<string, unknown>): boolean {
|
|
1843
|
+
const counts = countAnthropicStrictSchemaParameters(schema);
|
|
1844
|
+
return counts.optional <= 24 && counts.unions <= 16;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
function convertTools(tools: Tool[], isOAuthToken: boolean, forcedStrictToolName?: string): Anthropic.Messages.Tool[] {
|
|
1765
1848
|
if (!tools) return [];
|
|
1766
1849
|
|
|
1767
1850
|
return tools.map(tool => {
|
|
1768
|
-
const jsonSchema = tool.parameters as
|
|
1851
|
+
const jsonSchema = tool.parameters as Record<string, unknown>;
|
|
1852
|
+
const wantsStrict = tool.name === forcedStrictToolName && tool.strict === true;
|
|
1853
|
+
const adapted = adaptSchemaForStrict(jsonSchema, wantsStrict);
|
|
1854
|
+
const useStrict = adapted.strict && isWithinAnthropicStrictSchemaLimits(adapted.schema);
|
|
1855
|
+
const inputSchema = useStrict ? adapted.schema : jsonSchema;
|
|
1856
|
+
const anthropicInputSchema: Anthropic.Messages.Tool.InputSchema = {
|
|
1857
|
+
...inputSchema,
|
|
1858
|
+
type: "object",
|
|
1859
|
+
properties: (inputSchema.properties as Record<string, unknown> | undefined) ?? {},
|
|
1860
|
+
required: Array.isArray(inputSchema.required) ? (inputSchema.required as string[]) : [],
|
|
1861
|
+
};
|
|
1769
1862
|
|
|
1770
1863
|
return {
|
|
1771
1864
|
name: isOAuthToken ? applyClaudeToolPrefix(tool.name) : tool.name,
|
|
1772
1865
|
description: tool.description || "",
|
|
1773
|
-
input_schema:
|
|
1774
|
-
|
|
1775
|
-
properties: jsonSchema.properties || {},
|
|
1776
|
-
required: jsonSchema.required || [],
|
|
1777
|
-
},
|
|
1866
|
+
input_schema: anthropicInputSchema,
|
|
1867
|
+
...(useStrict ? { strict: true } : {}),
|
|
1778
1868
|
};
|
|
1779
1869
|
});
|
|
1780
1870
|
}
|