veryfront 0.1.381 → 0.1.382

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.
Files changed (33) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/provider/index.d.ts +3 -0
  3. package/esm/src/provider/index.d.ts.map +1 -1
  4. package/esm/src/provider/index.js +1 -0
  5. package/esm/src/provider/runtime-loader.d.ts +1 -11
  6. package/esm/src/provider/runtime-loader.d.ts.map +1 -1
  7. package/esm/src/provider/runtime-loader.js +2 -775
  8. package/esm/src/provider/shared/index.d.ts +2 -1
  9. package/esm/src/provider/shared/index.d.ts.map +1 -1
  10. package/esm/src/provider/shared/index.js +3 -1
  11. package/esm/src/provider/veryfront-cloud/model-catalog.d.ts +31 -0
  12. package/esm/src/provider/veryfront-cloud/model-catalog.d.ts.map +1 -0
  13. package/esm/src/provider/veryfront-cloud/model-catalog.js +163 -0
  14. package/esm/src/provider/veryfront-cloud/provider.d.ts.map +1 -1
  15. package/esm/src/provider/veryfront-cloud/provider.js +1 -7
  16. package/esm/src/utils/version-constant.d.ts +1 -1
  17. package/esm/src/utils/version-constant.js +1 -1
  18. package/package.json +1 -1
  19. package/src/deno.js +1 -1
  20. package/src/deps/esm.sh/@types/react-dom@19.2.3/client.d.ts +1 -1
  21. package/src/deps/esm.sh/@types/{react@19.2.3 → react@19.2.14}/global.d.ts +1 -0
  22. package/src/deps/esm.sh/@types/{react@19.2.3 → react@19.2.14}/index.d.ts +93 -24
  23. package/src/deps/esm.sh/react-dom@19.2.4/client.d.ts +1 -1
  24. package/src/src/provider/index.ts +20 -0
  25. package/src/src/provider/runtime-loader.ts +1 -1008
  26. package/src/src/provider/shared/index.ts +3 -1
  27. package/src/src/provider/veryfront-cloud/model-catalog.ts +220 -0
  28. package/src/src/provider/veryfront-cloud/provider.ts +3 -7
  29. package/src/src/utils/version-constant.ts +1 -1
  30. package/esm/src/provider/runtime-loader/provider-finish-reasons.d.ts +0 -9
  31. package/esm/src/provider/runtime-loader/provider-finish-reasons.d.ts.map +0 -1
  32. package/esm/src/provider/runtime-loader/provider-finish-reasons.js +0 -60
  33. package/src/src/provider/runtime-loader/provider-finish-reasons.ts +0 -69
@@ -1,14 +1,5 @@
1
- import type { ModelRuntime } from "./types.js";
2
- import { getAnthropicMessagesUrl } from "./runtime-loader/provider-endpoints.js";
3
1
  import { isNumberArray } from "./runtime-loader/provider-embedding-responses.js";
4
- import { normalizeAnthropicFinishReason } from "./runtime-loader/provider-finish-reasons.js";
5
- import { createAnthropicRequestInit } from "./runtime-loader/provider-request-init.js";
6
- import { parseSseChunk } from "./runtime-loader/provider-sse.js";
7
- import {
8
- extractAnthropicUsage,
9
- mergeUsage,
10
- type RuntimeUsage,
11
- } from "./runtime-loader/provider-usage.js";
2
+ import { mergeUsage, type RuntimeUsage } from "./runtime-loader/provider-usage.js";
12
3
  import type { ProviderKind } from "./runtime-loader/provider-http.js";
13
4
  import {
14
5
  buildProviderError,
@@ -35,21 +26,12 @@ export {
35
26
  isNumberArray,
36
27
  mergeUsage,
37
28
  parseRetryAfterMs,
38
- parseSseChunk,
39
29
  readRecord,
40
30
  requestJson,
41
31
  requestStream,
42
32
  };
43
33
  export type { RuntimeUsage };
44
34
 
45
- export interface AnthropicRuntimeConfig {
46
- apiKey?: string;
47
- authToken?: string;
48
- baseURL?: string;
49
- name?: string;
50
- fetch?: typeof globalThis.fetch;
51
- }
52
-
53
35
  export type RuntimePromptMessage =
54
36
  | { role: "system"; content: string }
55
37
  | { role: "user"; content: Array<{ type: "text"; text: string }> }
@@ -323,37 +305,6 @@ export type OpenAICompatibleChatRequest = {
323
305
  frequency_penalty?: number;
324
306
  [key: string]: unknown;
325
307
  };
326
- type AnthropicCompatibleMessage = {
327
- role: "user" | "assistant";
328
- content: Array<Record<string, unknown>>;
329
- };
330
- type AnthropicCompatibleRequest = {
331
- model: string;
332
- messages: AnthropicCompatibleMessage[];
333
- max_tokens: number;
334
- stream?: boolean;
335
- /**
336
- * String form is the classic shorthand. Array-of-blocks form is required
337
- * when the system prompt carries a cache_control breakpoint, because
338
- * cache_control lives on an individual content block, not on a raw string.
339
- */
340
- system?: string | Array<Record<string, unknown>>;
341
- temperature?: number;
342
- top_p?: number;
343
- stop_sequences?: string[];
344
- tools?: Array<Record<string, unknown>>;
345
- tool_choice?: unknown;
346
- [key: string]: unknown;
347
- };
348
- type AnthropicStreamToolCallState = {
349
- id: string;
350
- name: string;
351
- input: string;
352
- providerExecuted?: boolean;
353
- };
354
- type AnthropicStreamReasoningState = {
355
- id: string;
356
- };
357
308
  /**
358
309
  * Structured warning emitted when a provider runtime drops or rewrites a
359
310
  * caller-provided option. Mirrors the AI ecosystem convention (Vercel AI
@@ -513,886 +464,6 @@ export function readProviderOptions(
513
464
  return merged;
514
465
  }
515
466
 
516
- function normalizeAnthropicToolChoice(toolChoice: unknown): unknown {
517
- if (typeof toolChoice === "string") {
518
- return { type: toolChoice };
519
- }
520
-
521
- return toolChoice;
522
- }
523
-
524
- function toSnakeCaseRecord(record: Record<string, unknown>): Record<string, unknown> {
525
- return Object.fromEntries(
526
- Object.entries(record).map(([key, value]) => [
527
- key.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`),
528
- value,
529
- ]),
530
- );
531
- }
532
-
533
- /**
534
- * Recursive snake_case key converter for nested config objects (used for
535
- * Anthropic mcp_servers, where authorizationToken / toolConfiguration /
536
- * allowedTools all need conversion).
537
- */
538
- function deepSnakeCase(value: unknown): unknown {
539
- if (Array.isArray(value)) {
540
- return value.map(deepSnakeCase);
541
- }
542
- if (value !== null && typeof value === "object") {
543
- return Object.fromEntries(
544
- Object.entries(value as Record<string, unknown>).map(([key, v]) => [
545
- key.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`),
546
- deepSnakeCase(v),
547
- ]),
548
- );
549
- }
550
- return value;
551
- }
552
-
553
- function pushAnthropicUserContent(
554
- messages: AnthropicCompatibleMessage[],
555
- content: Array<Record<string, unknown>>,
556
- ): void {
557
- if (content.length === 0) {
558
- return;
559
- }
560
-
561
- const lastMessage = messages.at(-1);
562
- if (lastMessage?.role === "user") {
563
- lastMessage.content.push(...content);
564
- return;
565
- }
566
-
567
- messages.push({
568
- role: "user",
569
- content,
570
- });
571
- }
572
-
573
- /**
574
- * Resolves a {@link ProviderCacheTtl} into Anthropic's `cache_control` shape.
575
- *
576
- * Returns `undefined` when caching is not requested (`false` / `undefined`),
577
- * `{ type: "ephemeral" }` for the 5-minute default (`true` / `"5m"`), or
578
- * `{ type: "ephemeral", ttl: "1h" }` for the extended 1-hour cache.
579
- */
580
- function resolveAnthropicCacheControlBlock(
581
- ttl: ProviderCacheTtl | undefined,
582
- ): { type: "ephemeral"; ttl?: "1h" } | undefined {
583
- if (ttl === undefined || ttl === false) {
584
- return undefined;
585
- }
586
- if (ttl === "1h") {
587
- return { type: "ephemeral", ttl: "1h" };
588
- }
589
- return { type: "ephemeral" };
590
- }
591
-
592
- function toAnthropicMessages(
593
- prompt: RuntimePromptMessage[],
594
- systemCacheControl?: { type: "ephemeral"; ttl?: "1h" },
595
- ): {
596
- system?: string | Array<Record<string, unknown>>;
597
- messages: AnthropicCompatibleMessage[];
598
- } {
599
- const systemParts: string[] = [];
600
- const messages: AnthropicCompatibleMessage[] = [];
601
-
602
- for (const message of prompt) {
603
- switch (message.role) {
604
- case "system":
605
- if (message.content.length > 0) {
606
- systemParts.push(message.content);
607
- }
608
- break;
609
- case "user":
610
- pushAnthropicUserContent(messages, [{
611
- type: "text",
612
- text: readTextParts(message.content),
613
- }]);
614
- break;
615
- case "assistant":
616
- messages.push({
617
- role: "assistant",
618
- content: message.content.map((part) => {
619
- if (part.type === "text") {
620
- return { type: "text", text: part.text };
621
- }
622
- if (part.type === "reasoning") {
623
- // Redacted thinking blocks roundtrip as the encrypted blob
624
- // form Anthropic gave us. Plain thinking blocks need the
625
- // signature to verify on the server.
626
- if (typeof part.redactedData === "string") {
627
- return {
628
- type: "redacted_thinking",
629
- data: part.redactedData,
630
- };
631
- }
632
- return {
633
- type: "thinking",
634
- thinking: part.text ?? "",
635
- ...(typeof part.signature === "string" ? { signature: part.signature } : {}),
636
- };
637
- }
638
- return {
639
- type: "tool_use",
640
- id: part.toolCallId,
641
- name: part.toolName,
642
- input: part.input,
643
- };
644
- }),
645
- });
646
- break;
647
- case "tool":
648
- pushAnthropicUserContent(
649
- messages,
650
- message.content.map((part) => ({
651
- type: "tool_result",
652
- tool_use_id: part.toolCallId,
653
- content: stringifyJsonValue(part.output.value),
654
- })),
655
- );
656
- break;
657
- }
658
- }
659
-
660
- if (systemParts.length === 0) {
661
- return { messages };
662
- }
663
-
664
- const joined = systemParts.join("\n\n");
665
-
666
- // Cache-controlled system prompts must use the array-of-blocks form so the
667
- // breakpoint lands on an individual content block. Callers that don't opt
668
- // in keep the legacy raw-string form for backward compatibility.
669
- if (systemCacheControl) {
670
- return {
671
- system: [{
672
- type: "text",
673
- text: joined,
674
- cache_control: systemCacheControl,
675
- }],
676
- messages,
677
- };
678
- }
679
-
680
- return { system: joined, messages };
681
- }
682
-
683
- /**
684
- * Short-name → latest-versioned-type alias map for Anthropic provider tools.
685
- *
686
- * Anthropic tool types are date-stamped (e.g. `code_execution_20260120`) so
687
- * callers either pin a version or get the latest. We accept both: a caller
688
- * can pass `anthropic.code_execution` and we map to the latest known version,
689
- * or pass `anthropic.code_execution_20250522` and we forward verbatim.
690
- *
691
- * Versions chosen here are the latest documented releases as of 2026-04-15
692
- * — see https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview.
693
- * When Anthropic ships newer versions, update this map.
694
- */
695
- const ANTHROPIC_TOOL_VERSION_ALIASES: Record<string, string> = {
696
- code_execution: "code_execution_20260120",
697
- computer_use: "computer_20250124",
698
- computer: "computer_20250124",
699
- text_editor: "text_editor_20250728",
700
- bash: "bash_20250124",
701
- memory: "memory_20250818",
702
- web_search: "web_search_20250305",
703
- web_fetch: "web_fetch_20250910",
704
- };
705
-
706
- function resolveAnthropicProviderType(rawType: string): string {
707
- // Already-versioned types (contain a date stamp suffix) pass through verbatim.
708
- if (/_\d{8}$/.test(rawType)) {
709
- return rawType;
710
- }
711
- return ANTHROPIC_TOOL_VERSION_ALIASES[rawType] ?? rawType;
712
- }
713
-
714
- function toAnthropicTools(
715
- tools: RuntimeToolDefinition[] | undefined,
716
- toolsCacheControl?: { type: "ephemeral"; ttl?: "1h" },
717
- ): Array<Record<string, unknown>> | undefined {
718
- if (!tools) {
719
- return undefined;
720
- }
721
-
722
- const normalized: Array<Record<string, unknown>> = [];
723
-
724
- for (const tool of tools) {
725
- if (tool.type === "function") {
726
- normalized.push({
727
- name: tool.name,
728
- ...(typeof tool.description === "string" ? { description: tool.description } : {}),
729
- input_schema: unwrapToolInputSchema(tool.inputSchema),
730
- });
731
- continue;
732
- }
733
-
734
- if (!tool.id.startsWith("anthropic.")) {
735
- continue;
736
- }
737
-
738
- const rawType = tool.id.slice("anthropic.".length);
739
- if (rawType.length === 0) {
740
- continue;
741
- }
742
-
743
- normalized.push({
744
- type: resolveAnthropicProviderType(rawType),
745
- name: tool.name,
746
- ...toSnakeCaseRecord(tool.args),
747
- });
748
- }
749
-
750
- if (normalized.length === 0) {
751
- return undefined;
752
- }
753
-
754
- // Attach the cache breakpoint to the final tool entry so Anthropic caches
755
- // the entire tools block up to and including that definition. Earlier tool
756
- // entries are implicitly covered by the same breakpoint per Anthropic's
757
- // walk-backward cache lookup behaviour.
758
- if (toolsCacheControl) {
759
- const lastIndex = normalized.length - 1;
760
- normalized[lastIndex] = {
761
- ...normalized[lastIndex],
762
- cache_control: toolsCacheControl,
763
- };
764
- }
765
-
766
- return normalized;
767
- }
768
-
769
- /**
770
- * Anthropic's Messages API requires `max_tokens` on every call, so the
771
- * outbound request builder must always supply a number. Picking the right
772
- * one means knowing the model: different Claude families have wildly
773
- * different maximum output budgets, and a flat default either truncates
774
- * modern models mid-response or gets rejected with "too many tokens" on
775
- * older ones. Return the model's advertised maximum as the default, and
776
- * clamp caller-provided values at that same ceiling for known models so a
777
- * bad input becomes a clipped response rather than an API error. Unknown
778
- * model ids get a conservative 4096 fallback and pass caller values
779
- * through unchanged, since we have no intel to clamp against.
780
- */
781
- function getAnthropicModelCapabilities(
782
- modelId: string,
783
- ): { maxOutputTokens: number; isKnownModel: boolean } {
784
- if (modelId.includes("claude-sonnet-4-6") || modelId.includes("claude-opus-4-6")) {
785
- return { maxOutputTokens: 128_000, isKnownModel: true };
786
- }
787
- if (
788
- modelId.includes("claude-sonnet-4-5") ||
789
- modelId.includes("claude-opus-4-5") ||
790
- modelId.includes("claude-haiku-4-5")
791
- ) {
792
- return { maxOutputTokens: 64_000, isKnownModel: true };
793
- }
794
- if (modelId.includes("claude-opus-4-1")) {
795
- return { maxOutputTokens: 32_000, isKnownModel: true };
796
- }
797
- if (modelId.includes("claude-sonnet-4-")) {
798
- return { maxOutputTokens: 64_000, isKnownModel: true };
799
- }
800
- if (modelId.includes("claude-opus-4-")) {
801
- return { maxOutputTokens: 32_000, isKnownModel: true };
802
- }
803
- if (modelId.includes("claude-3-haiku")) {
804
- return { maxOutputTokens: 4096, isKnownModel: true };
805
- }
806
- return { maxOutputTokens: 4096, isKnownModel: false };
807
- }
808
-
809
- function resolveAnthropicMaxTokens(
810
- modelId: string,
811
- callerMaxOutputTokens: number | undefined,
812
- ): number {
813
- const { maxOutputTokens: modelMax, isKnownModel } = getAnthropicModelCapabilities(modelId);
814
- const requested = callerMaxOutputTokens ?? modelMax;
815
- if (isKnownModel && requested > modelMax) {
816
- return modelMax;
817
- }
818
- return requested;
819
- }
820
-
821
- /**
822
- * Map a unified reasoning effort level to an Anthropic `thinking.budget_tokens`
823
- * value. Anthropic's minimum accepted budget is 1024; higher tiers give Claude
824
- * more headroom to explore. `max` maps to the upper bound documented for
825
- * Claude 4.x family (32k tokens of thinking — caller can override via
826
- * `budgetTokens` if they need more).
827
- */
828
- function resolveAnthropicThinkingBudget(
829
- option: ProviderReasoningOption | undefined,
830
- ): number | undefined {
831
- if (!option || option.enabled !== true) {
832
- return undefined;
833
- }
834
- if (typeof option.budgetTokens === "number" && option.budgetTokens >= 1024) {
835
- return option.budgetTokens;
836
- }
837
- switch (option.effort) {
838
- case "low":
839
- return 1024;
840
- case "high":
841
- return 16_384;
842
- case "max":
843
- return 32_768;
844
- case "medium":
845
- default:
846
- return 4096;
847
- }
848
- }
849
-
850
- function buildAnthropicMessagesRequest(
851
- modelId: string,
852
- providerName: string,
853
- options: OpenAICompatibleLanguageOptions,
854
- stream: boolean,
855
- warnings: WarningCollector,
856
- ): AnthropicCompatibleRequest {
857
- const systemCacheControl = resolveAnthropicCacheControlBlock(
858
- options.cacheControl?.system,
859
- );
860
- const toolsCacheControl = resolveAnthropicCacheControlBlock(
861
- options.cacheControl?.tools,
862
- );
863
-
864
- const { system, messages } = toAnthropicMessages(options.prompt, systemCacheControl);
865
- const anthropicTools = toAnthropicTools(options.tools, toolsCacheControl);
866
- const thinkingBudget = resolveAnthropicThinkingBudget(options.reasoning);
867
- const thinkingEnabled = thinkingBudget !== undefined;
868
-
869
- // Anthropic doesn't support these unified options at all — emit warnings
870
- // so callers don't quietly pass values that have zero effect.
871
- if (options.presencePenalty !== undefined) {
872
- warnings.push({
873
- type: "unsupported-setting",
874
- provider: "anthropic",
875
- setting: "presencePenalty",
876
- details: "Anthropic Messages API has no equivalent and the value was dropped.",
877
- });
878
- }
879
- if (options.frequencyPenalty !== undefined) {
880
- warnings.push({
881
- type: "unsupported-setting",
882
- provider: "anthropic",
883
- setting: "frequencyPenalty",
884
- details: "Anthropic Messages API has no equivalent and the value was dropped.",
885
- });
886
- }
887
- if (options.seed !== undefined) {
888
- warnings.push({
889
- type: "unsupported-setting",
890
- provider: "anthropic",
891
- setting: "seed",
892
- details: "Anthropic Messages API does not support deterministic seeding.",
893
- });
894
- }
895
- if (options.topK !== undefined) {
896
- warnings.push({
897
- type: "unsupported-setting",
898
- provider: "anthropic",
899
- setting: "topK",
900
- details: "Anthropic Messages API does not expose top_k on this surface.",
901
- });
902
- }
903
- if (
904
- options.stopSequences && options.stopSequences.length > 4
905
- ) {
906
- warnings.push({
907
- type: "unsupported-setting",
908
- provider: "anthropic",
909
- setting: "stopSequences",
910
- details:
911
- `Anthropic accepts at most 4 stop sequences; ${options.stopSequences.length} were provided and the extras were truncated.`,
912
- });
913
- }
914
- if (thinkingEnabled && options.temperature !== undefined) {
915
- warnings.push({
916
- type: "unsupported-setting",
917
- provider: "anthropic",
918
- setting: "temperature",
919
- details:
920
- "Dropped because Anthropic rejects sampling params when extended thinking is enabled.",
921
- });
922
- }
923
- if (thinkingEnabled && options.topP !== undefined) {
924
- warnings.push({
925
- type: "unsupported-setting",
926
- provider: "anthropic",
927
- setting: "topP",
928
- details:
929
- "Dropped because Anthropic rejects sampling params when extended thinking is enabled.",
930
- });
931
- }
932
- if (options.responseFormat && options.responseFormat.type !== "text") {
933
- warnings.push({
934
- type: "unsupported-setting",
935
- provider: "anthropic",
936
- setting: "responseFormat",
937
- details:
938
- "Anthropic Messages API does not have a structured-output response_format equivalent. Use a tool with the schema as input_schema instead.",
939
- });
940
- }
941
-
942
- // Anthropic requires max_tokens > budget_tokens when thinking is enabled.
943
- // Growing max_tokens by the thinking budget preserves the caller's intended
944
- // output budget, and we clamp the sum at the model's advertised maximum so
945
- // the request never exceeds the API's hard cap.
946
- const baseMaxTokens = resolveAnthropicMaxTokens(modelId, options.maxOutputTokens);
947
- const maxTokens = thinkingEnabled
948
- ? Math.min(
949
- baseMaxTokens + (thinkingBudget ?? 0),
950
- getAnthropicModelCapabilities(modelId).maxOutputTokens,
951
- )
952
- : baseMaxTokens;
953
-
954
- const body: AnthropicCompatibleRequest = {
955
- model: modelId,
956
- messages,
957
- max_tokens: maxTokens,
958
- ...(stream ? { stream: true } : {}),
959
- ...(system ? { system } : {}),
960
- // Sampling params are mutually exclusive with thinking on Anthropic — the
961
- // API rejects the combo outright. Drop them silently when thinking is on
962
- // (callers see thinking's output instead of what they'd have gotten from
963
- // custom sampling, which is the documented tradeoff).
964
- ...(!thinkingEnabled && options.temperature !== undefined
965
- ? { temperature: options.temperature }
966
- : {}),
967
- ...(!thinkingEnabled && options.topP !== undefined ? { top_p: options.topP } : {}),
968
- ...(options.stopSequences && options.stopSequences.length > 0
969
- ? { stop_sequences: options.stopSequences.slice(0, 4) }
970
- : {}),
971
- ...(anthropicTools ? { tools: anthropicTools } : {}),
972
- ...(options.toolChoice !== undefined
973
- ? { tool_choice: normalizeAnthropicToolChoice(options.toolChoice) }
974
- : {}),
975
- ...(thinkingEnabled ? { thinking: { type: "enabled", budget_tokens: thinkingBudget } } : {}),
976
- ...(typeof options.userId === "string" && options.userId.length > 0
977
- ? { metadata: { user_id: options.userId } }
978
- : {}),
979
- ...(options.mcpServers && options.mcpServers.length > 0
980
- ? { mcp_servers: deepSnakeCase(options.mcpServers) as unknown[] }
981
- : {}),
982
- ...(options.anthropicContainer !== undefined ? { container: options.anthropicContainer } : {}),
983
- };
984
-
985
- Object.assign(body, readProviderOptions(options.providerOptions, "anthropic", providerName));
986
- return body;
987
- }
988
-
989
- type AnthropicReasoningContent = {
990
- type: "reasoning";
991
- text?: string;
992
- signature?: string;
993
- redactedData?: string;
994
- };
995
-
996
- type AnthropicCitation = {
997
- type: string;
998
- citedText?: string;
999
- url?: string;
1000
- title?: string;
1001
- startCharIndex?: number;
1002
- endCharIndex?: number;
1003
- startBlockIndex?: number;
1004
- endBlockIndex?: number;
1005
- startPageNumber?: number;
1006
- endPageNumber?: number;
1007
- documentIndex?: number;
1008
- documentTitle?: string;
1009
- };
1010
-
1011
- type AnthropicTextContent = {
1012
- type: "text";
1013
- text: string;
1014
- citations?: AnthropicCitation[];
1015
- };
1016
-
1017
- /**
1018
- * Best-effort camelCase normalization of a single Anthropic citation
1019
- * record. Handles the union of fields across web_search_result_location,
1020
- * web_fetch_result_location, char_location, page_location, and
1021
- * content_block_location citation kinds — see
1022
- * https://docs.claude.com/en/docs/build-with-claude/citations
1023
- */
1024
- function normalizeAnthropicCitation(raw: unknown): AnthropicCitation | undefined {
1025
- const r = readRecord(raw);
1026
- if (!r) return undefined;
1027
- const typeStr = typeof r.type === "string" ? r.type : undefined;
1028
- if (!typeStr) return undefined;
1029
- const out: AnthropicCitation = { type: typeStr };
1030
- if (typeof r.cited_text === "string") out.citedText = r.cited_text;
1031
- if (typeof r.url === "string") out.url = r.url;
1032
- if (typeof r.title === "string") out.title = r.title;
1033
- if (typeof r.start_char_index === "number") out.startCharIndex = r.start_char_index;
1034
- if (typeof r.end_char_index === "number") out.endCharIndex = r.end_char_index;
1035
- if (typeof r.start_block_index === "number") out.startBlockIndex = r.start_block_index;
1036
- if (typeof r.end_block_index === "number") out.endBlockIndex = r.end_block_index;
1037
- if (typeof r.start_page_number === "number") out.startPageNumber = r.start_page_number;
1038
- if (typeof r.end_page_number === "number") out.endPageNumber = r.end_page_number;
1039
- if (typeof r.document_index === "number") out.documentIndex = r.document_index;
1040
- if (typeof r.document_title === "string") out.documentTitle = r.document_title;
1041
- return out;
1042
- }
1043
-
1044
- function buildAnthropicGenerateResult(payload: unknown): {
1045
- content: Array<
1046
- | AnthropicTextContent
1047
- | AnthropicReasoningContent
1048
- | { type: "tool-call"; toolCallId: string; toolName: string; input: string }
1049
- | { type: "tool-result"; toolCallId: string; toolName: string; result: unknown }
1050
- >;
1051
- finishReason?: string | { unified: string; raw: string } | null;
1052
- usage?: RuntimeUsage;
1053
- } {
1054
- const record = readRecord(payload);
1055
- const content = Array.isArray(record?.content) ? record.content : [];
1056
- const normalized: Array<
1057
- | AnthropicTextContent
1058
- | AnthropicReasoningContent
1059
- | { type: "tool-call"; toolCallId: string; toolName: string; input: string }
1060
- | { type: "tool-result"; toolCallId: string; toolName: string; result: unknown }
1061
- > = [];
1062
-
1063
- for (const blockValue of content) {
1064
- const block = readRecord(blockValue);
1065
- const blockType = typeof block?.type === "string" ? block.type : undefined;
1066
-
1067
- if (blockType === "text" && typeof block?.text === "string" && block.text.length > 0) {
1068
- const citationsRaw = Array.isArray(block.citations) ? block.citations : undefined;
1069
- const citations = citationsRaw
1070
- ?.flatMap((c) => {
1071
- const normalizedCitation = normalizeAnthropicCitation(c);
1072
- return normalizedCitation ? [normalizedCitation] : [];
1073
- });
1074
- normalized.push({
1075
- type: "text",
1076
- text: block.text,
1077
- ...(citations && citations.length > 0 ? { citations } : {}),
1078
- });
1079
- continue;
1080
- }
1081
-
1082
- // Thinking blocks carry the cleartext trace plus a signature that
1083
- // Anthropic uses to verify on subsequent turns. Surfacing both lets
1084
- // callers persist them as `reasoning` content parts and replay on
1085
- // the next turn so Claude can continue from the same thinking.
1086
- if (blockType === "thinking") {
1087
- normalized.push({
1088
- type: "reasoning",
1089
- ...(typeof block?.thinking === "string" ? { text: block.thinking } : {}),
1090
- ...(typeof block?.signature === "string" ? { signature: block.signature } : {}),
1091
- });
1092
- continue;
1093
- }
1094
-
1095
- // Redacted thinking blocks arrive when Claude's safety classifier
1096
- // hides the trace. Pass the encrypted blob through opaquely so the
1097
- // caller can replay it on the next turn (Anthropic still needs the
1098
- // blob to verify continuity even though it can't read it).
1099
- if (blockType === "redacted_thinking" && typeof block?.data === "string") {
1100
- normalized.push({
1101
- type: "reasoning",
1102
- redactedData: block.data,
1103
- });
1104
- continue;
1105
- }
1106
-
1107
- if (
1108
- (blockType === "tool_use" || blockType === "server_tool_use") &&
1109
- typeof block?.id === "string" &&
1110
- typeof block?.name === "string"
1111
- ) {
1112
- normalized.push({
1113
- type: "tool-call",
1114
- toolCallId: block.id,
1115
- toolName: block.name,
1116
- input: stringifyJsonValue(block.input ?? {}),
1117
- });
1118
- continue;
1119
- }
1120
-
1121
- if (
1122
- blockType === "web_search_tool_result" &&
1123
- typeof block?.tool_use_id === "string" &&
1124
- Array.isArray(block?.content)
1125
- ) {
1126
- normalized.push({
1127
- type: "tool-result",
1128
- toolCallId: block.tool_use_id,
1129
- toolName: "web_search",
1130
- result: block.content,
1131
- });
1132
- }
1133
-
1134
- if (
1135
- blockType === "web_fetch_tool_result" &&
1136
- typeof block?.tool_use_id === "string" &&
1137
- readRecord(block?.content)
1138
- ) {
1139
- normalized.push({
1140
- type: "tool-result",
1141
- toolCallId: block.tool_use_id,
1142
- toolName: "web_fetch",
1143
- result: block.content,
1144
- });
1145
- }
1146
- }
1147
-
1148
- return {
1149
- content: normalized,
1150
- finishReason: normalizeAnthropicFinishReason(record?.stop_reason),
1151
- usage: extractAnthropicUsage(payload),
1152
- };
1153
- }
1154
-
1155
- async function* streamAnthropicCompatibleParts(
1156
- stream: ReadableStream<Uint8Array>,
1157
- ): AsyncIterable<unknown> {
1158
- const decoder = new TextDecoder();
1159
- let buffer = "";
1160
- const toolCalls = new Map<number, AnthropicStreamToolCallState>();
1161
- const reasoningBlocks = new Map<number, AnthropicStreamReasoningState>();
1162
- let finishReason: string | { unified: string; raw: string } | null = null;
1163
- let usage: RuntimeUsage | undefined;
1164
-
1165
- for await (const chunk of stream) {
1166
- buffer += decoder.decode(chunk, { stream: true });
1167
- const parsed = parseSseChunk(buffer);
1168
- buffer = parsed.remainder;
1169
-
1170
- for (const event of parsed.events) {
1171
- if (event === "[DONE]") {
1172
- continue;
1173
- }
1174
-
1175
- const record = readRecord(event);
1176
- const eventType = typeof record?.type === "string" ? record.type : undefined;
1177
- usage = mergeUsage(usage, extractAnthropicUsage(record));
1178
-
1179
- if (eventType === "message_start") {
1180
- usage = mergeUsage(usage, extractAnthropicUsage(record?.message));
1181
- continue;
1182
- }
1183
-
1184
- if (eventType === "content_block_start") {
1185
- const index = typeof record?.index === "number" ? record.index : 0;
1186
- const contentBlock = readRecord(record?.content_block);
1187
- const blockType = typeof contentBlock?.type === "string" ? contentBlock.type : undefined;
1188
-
1189
- if (
1190
- blockType === "text" && typeof contentBlock?.text === "string" &&
1191
- contentBlock.text.length > 0
1192
- ) {
1193
- yield { type: "text-delta", delta: contentBlock.text };
1194
- continue;
1195
- }
1196
-
1197
- if (blockType === "thinking") {
1198
- const reasoningId = `thinking-${index}`;
1199
- reasoningBlocks.set(index, { id: reasoningId });
1200
- yield {
1201
- type: "reasoning-start",
1202
- id: reasoningId,
1203
- };
1204
-
1205
- if (typeof contentBlock?.thinking === "string" && contentBlock.thinking.length > 0) {
1206
- yield {
1207
- type: "reasoning-delta",
1208
- id: reasoningId,
1209
- delta: contentBlock.thinking,
1210
- };
1211
- }
1212
- continue;
1213
- }
1214
-
1215
- // Redacted thinking blocks arrive as opaque encrypted payloads when
1216
- // Claude's safety classifier flags the reasoning trace. Surface them
1217
- // as a zero-length reasoning block so callers know thinking happened
1218
- // without leaking the (legitimately hidden) contents.
1219
- if (blockType === "redacted_thinking") {
1220
- const reasoningId = `thinking-${index}`;
1221
- reasoningBlocks.set(index, { id: reasoningId });
1222
- yield {
1223
- type: "reasoning-start",
1224
- id: reasoningId,
1225
- };
1226
- continue;
1227
- }
1228
-
1229
- if (
1230
- (blockType === "tool_use" || blockType === "server_tool_use") &&
1231
- typeof contentBlock?.id === "string" &&
1232
- typeof contentBlock?.name === "string"
1233
- ) {
1234
- const providerExecuted = blockType === "server_tool_use" ? true : undefined;
1235
- const current: AnthropicStreamToolCallState = {
1236
- id: contentBlock.id,
1237
- name: contentBlock.name,
1238
- input: "",
1239
- ...(providerExecuted ? { providerExecuted } : {}),
1240
- };
1241
-
1242
- toolCalls.set(index, current);
1243
- yield {
1244
- type: "tool-input-start",
1245
- id: current.id,
1246
- toolName: current.name,
1247
- ...(providerExecuted ? { providerExecuted } : {}),
1248
- };
1249
-
1250
- const initialInput = contentBlock.input;
1251
- if (initialInput !== undefined) {
1252
- const serializedInput = stringifyJsonValue(initialInput);
1253
- current.input += serializedInput;
1254
- yield {
1255
- type: "tool-input-delta",
1256
- id: current.id,
1257
- delta: serializedInput,
1258
- };
1259
- }
1260
- continue;
1261
- }
1262
-
1263
- if (
1264
- blockType === "web_search_tool_result" &&
1265
- typeof contentBlock?.tool_use_id === "string" &&
1266
- Array.isArray(contentBlock?.content)
1267
- ) {
1268
- yield {
1269
- type: "tool-result",
1270
- toolCallId: contentBlock.tool_use_id,
1271
- toolName: "web_search",
1272
- result: contentBlock.content,
1273
- providerExecuted: true,
1274
- };
1275
- }
1276
-
1277
- if (
1278
- blockType === "web_fetch_tool_result" &&
1279
- typeof contentBlock?.tool_use_id === "string" &&
1280
- readRecord(contentBlock?.content)
1281
- ) {
1282
- yield {
1283
- type: "tool-result",
1284
- toolCallId: contentBlock.tool_use_id,
1285
- toolName: "web_fetch",
1286
- result: contentBlock.content,
1287
- providerExecuted: true,
1288
- };
1289
- }
1290
-
1291
- continue;
1292
- }
1293
-
1294
- if (eventType === "content_block_delta") {
1295
- const index = typeof record?.index === "number" ? record.index : 0;
1296
- const delta = readRecord(record?.delta);
1297
- const deltaType = typeof delta?.type === "string" ? delta.type : undefined;
1298
-
1299
- if (
1300
- deltaType === "text_delta" && typeof delta?.text === "string" && delta.text.length > 0
1301
- ) {
1302
- yield { type: "text-delta", delta: delta.text };
1303
- continue;
1304
- }
1305
-
1306
- if (
1307
- deltaType === "thinking_delta" && typeof delta?.thinking === "string" &&
1308
- delta.thinking.length > 0
1309
- ) {
1310
- const current = reasoningBlocks.get(index);
1311
- if (!current) {
1312
- continue;
1313
- }
1314
-
1315
- yield {
1316
- type: "reasoning-delta",
1317
- id: current.id,
1318
- delta: delta.thinking,
1319
- };
1320
- continue;
1321
- }
1322
-
1323
- if (deltaType === "input_json_delta" && typeof delta?.partial_json === "string") {
1324
- const current = toolCalls.get(index);
1325
- if (!current) {
1326
- continue;
1327
- }
1328
-
1329
- current.input += delta.partial_json;
1330
- yield {
1331
- type: "tool-input-delta",
1332
- id: current.id,
1333
- delta: delta.partial_json,
1334
- };
1335
- }
1336
-
1337
- continue;
1338
- }
1339
-
1340
- if (eventType === "content_block_stop") {
1341
- const index = typeof record?.index === "number" ? record.index : 0;
1342
- const reasoning = reasoningBlocks.get(index);
1343
- if (reasoning) {
1344
- yield {
1345
- type: "reasoning-end",
1346
- id: reasoning.id,
1347
- };
1348
- reasoningBlocks.delete(index);
1349
- continue;
1350
- }
1351
-
1352
- const current = toolCalls.get(index);
1353
- if (!current) {
1354
- continue;
1355
- }
1356
-
1357
- yield {
1358
- type: "tool-call",
1359
- toolCallId: current.id,
1360
- toolName: current.name,
1361
- input: current.input.length > 0 ? current.input : "{}",
1362
- ...(current.providerExecuted ? { providerExecuted: true } : {}),
1363
- };
1364
- toolCalls.delete(index);
1365
- continue;
1366
- }
1367
-
1368
- if (eventType === "message_delta") {
1369
- const delta = readRecord(record?.delta);
1370
- const normalizedFinishReason = normalizeAnthropicFinishReason(delta?.stop_reason);
1371
- if (normalizedFinishReason) {
1372
- finishReason = normalizedFinishReason;
1373
- }
1374
- }
1375
- }
1376
- }
1377
-
1378
- if (buffer.trim().length > 0) {
1379
- const parsed = parseSseChunk(`${buffer}\n\n`);
1380
- for (const event of parsed.events) {
1381
- if (event === "[DONE]") {
1382
- continue;
1383
- }
1384
- const record = readRecord(event);
1385
- usage = mergeUsage(usage, extractAnthropicUsage(record));
1386
- }
1387
- }
1388
-
1389
- yield {
1390
- type: "finish",
1391
- finishReason,
1392
- ...(usage ? { usage } : {}),
1393
- };
1394
- }
1395
-
1396
467
  export function unwrapToolInputSchema(inputSchema: unknown): unknown {
1397
468
  if (typeof inputSchema !== "object" || inputSchema === null || Array.isArray(inputSchema)) {
1398
469
  return inputSchema;
@@ -1401,81 +472,3 @@ export function unwrapToolInputSchema(inputSchema: unknown): unknown {
1401
472
  const candidate = Reflect.get(inputSchema, "jsonSchema");
1402
473
  return candidate ?? inputSchema;
1403
474
  }
1404
-
1405
- export function createAnthropicModelRuntime(
1406
- config: AnthropicRuntimeConfig,
1407
- modelId: string,
1408
- ): ModelRuntime {
1409
- const fetchImpl = config.fetch ?? globalThis.fetch;
1410
- return {
1411
- provider: config.name ?? "anthropic",
1412
- modelId,
1413
- specificationVersion: "v3",
1414
- supportedUrls: {},
1415
- doGenerate(optionsForRuntime: unknown) {
1416
- const options = optionsForRuntime as OpenAICompatibleLanguageOptions;
1417
- const url = getAnthropicMessagesUrl(config.baseURL);
1418
- const warnings = createWarningCollector();
1419
- const body = buildAnthropicMessagesRequest(
1420
- modelId,
1421
- config.name ?? "anthropic",
1422
- options,
1423
- false,
1424
- warnings,
1425
- );
1426
- return requestJson({
1427
- url,
1428
- fetchImpl,
1429
- providerLabel: config.name ?? "anthropic",
1430
- providerKind: "anthropic",
1431
- init: createAnthropicRequestInit({
1432
- apiKey: config.apiKey,
1433
- authToken: config.authToken,
1434
- extraHeaders: options.headers,
1435
- body: JSON.stringify(body),
1436
- signal: options.abortSignal,
1437
- }),
1438
- }).then((payload) => {
1439
- const drained = warnings.drain();
1440
- return {
1441
- ...buildAnthropicGenerateResult(payload),
1442
- ...(drained.length > 0 ? { warnings: drained } : {}),
1443
- };
1444
- });
1445
- },
1446
- doStream(optionsForRuntime: unknown) {
1447
- const options = optionsForRuntime as OpenAICompatibleLanguageOptions;
1448
- const url = getAnthropicMessagesUrl(config.baseURL);
1449
- const warnings = createWarningCollector();
1450
- const body = buildAnthropicMessagesRequest(
1451
- modelId,
1452
- config.name ?? "anthropic",
1453
- options,
1454
- true,
1455
- warnings,
1456
- );
1457
- return requestStream({
1458
- url,
1459
- fetchImpl,
1460
- providerLabel: config.name ?? "anthropic",
1461
- providerKind: "anthropic",
1462
- init: createAnthropicRequestInit({
1463
- apiKey: config.apiKey,
1464
- authToken: config.authToken,
1465
- extraHeaders: options.headers,
1466
- enableFineGrainedToolStreaming: true,
1467
- body: JSON.stringify(body),
1468
- signal: options.abortSignal,
1469
- }),
1470
- }).then((responseStream) => {
1471
- const drained = warnings.drain();
1472
- return {
1473
- stream: ReadableStream.from(
1474
- withToolInputStatusTransitions(streamAnthropicCompatibleParts(responseStream)),
1475
- ),
1476
- ...(drained.length > 0 ? { warnings: drained } : {}),
1477
- };
1478
- });
1479
- },
1480
- };
1481
- }