diff --git a/API.md b/API.md index 7879eb6..fa6ad20 100644 --- a/API.md +++ b/API.md @@ -336,6 +336,10 @@ composable integration value: | `streamfold/langchain` | `langchain` | | `streamfold/ag-ui` | `agUI` | +The Anthropic integration accepts both `client.messages.create({ ..., stream: true })` +and `client.beta.messages.create({ ..., stream: true })` directly. Text, thinking, +and other non-tool SDK events can remain in the stream and are ignored. + The OpenAI integration accepts `client.responses.create({ ..., stream: true })` directly, including SDK event interfaces for text and other non-tool events. Non-tool events are ignored; callers do not need to cast or filter the stream. diff --git a/CHANGELOG.md b/CHANGELOG.md index f763a36..00f2fd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Accept Anthropic stable and beta SDK message event interfaces directly in + the Anthropic adapter and managed reader. - Accept OpenAI SDK response stream event types directly in the OpenAI adapter and managed reader, including non-tool events. - Preserve Gemini Interactions tool arguments from `arguments_delta` events diff --git a/examples/tsconfig.json b/examples/tsconfig.json index 4623a24..2ae8283 100644 --- a/examples/tsconfig.json +++ b/examples/tsconfig.json @@ -5,5 +5,5 @@ "checkJs": true, "skipLibCheck": true }, - "files": ["assistant-ui.mjs", "vercel-ai.mjs", "custom.mjs", "../tests/langchain-sdk.ts", "../tests/gemini-sdk.ts", "../tests/openai-sdk.ts"] + "files": ["assistant-ui.mjs", "vercel-ai.mjs", "custom.mjs", "../tests/langchain-sdk.ts", "../tests/gemini-sdk.ts", "../tests/openai-sdk.ts", "../tests/anthropic-sdk.ts"] } diff --git a/package.json b/package.json index 2b8b2a1..14e6f3a 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "verify": "pnpm check && pnpm pack:check && pnpm bench && pnpm bench:sdk" }, "devDependencies": { + "@anthropic-ai/sdk": "1.3.0", "@google/genai": "2.31.0", "@langchain/core": "1.3.0", "ai": "7.0.152", diff --git a/packages/core/src/anthropic.d.ts b/packages/core/src/anthropic.d.ts index f073497..16daa4c 100644 --- a/packages/core/src/anthropic.d.ts +++ b/packages/core/src/anthropic.d.ts @@ -28,6 +28,7 @@ export type AnthropicToolInputEvent = readonly type: "content_block_stop"; readonly index: number; } + | { readonly type: string } | { readonly type: string; readonly index?: number; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86b43b9..a114986 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,15 @@ importers: .: devDependencies: + '@anthropic-ai/sdk': + specifier: 1.3.0 + version: 1.3.0(zod@4.6.7) '@google/genai': specifier: 2.31.0 version: 2.31.0 '@langchain/core': specifier: 1.3.0 - version: 1.3.0(openai@7.45.1(google-auth-library@10.9.1)(undici@7.30.1)(ws@8.22.0)(zod@4.6.7))(ws@8.22.0) + version: 1.3.0(@opentelemetry/api@1.10.0)(openai@7.45.1(google-auth-library@10.9.1)(undici@7.30.1)(ws@8.22.0)(zod@4.6.7))(ws@8.22.0) ai: specifier: 7.0.152 version: 7.0.152(zod@4.6.7) @@ -62,6 +65,19 @@ packages: resolution: {integrity: sha512-C6ylMuAXjgqKJllp6ODIJwUltK/tm3ZmIj4V2fqENP3Y8gif2DyeJSsTcDwFrXppgEKcrBisi/BWx2tYeoeCAw==} engines: {node: '>=22'} + '@anthropic-ai/sdk@1.3.0': + resolution: {integrity: sha512-k7z9BlLLPxfZH+ov7dS6EnaN/wiZeV8YNT1LZfrpAHIy7oY7pHr4UzirF9f2OlHtFEnRBOX+AZQ91Y5fK+TPTw==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@babel/runtime@7.29.10': + resolution: {integrity: sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==} + engines: {node: '>=6.9.0'} + '@cfworker/json-schema@4.1.1': resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} @@ -78,6 +94,10 @@ packages: resolution: {integrity: sha512-ZiS8USsHdO8ZVFh0m8Rs6v2BvqBEvfFicG66zxdF4rGgXSJjk0d0sLnnFZCx3RXPUvP8ybTJgPlNfkJGs1J7BA==} engines: {node: '>=20'} + '@opentelemetry/api@1.10.0': + resolution: {integrity: sha512-lMkyes8iHrG+mk6WH7X70MdCLI2RS0FwNBY1PyQ8UemaijE2m5mv5A0Niy3HtbYTFrgkUEQjFMXmSAk2hQDuPQ==} + engines: {node: '>=8.0.0'} + '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -108,6 +128,9 @@ packages: '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + '@stablelib/base64@1.0.1': + resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -319,6 +342,9 @@ packages: resolution: {integrity: sha512-iK2f+YrcmoeGqk6fA0ea2bptcu/itMIm4NfEozq6N25+aG6h7s5HZbB/k1aV7b5w5sFLMCbbtRUsTVR+BgC3xw==} engines: {node: '>=12.17.0'} + fast-sha256@1.3.0: + resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -366,6 +392,10 @@ packages: json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} @@ -502,6 +532,9 @@ packages: secure-json-parse@4.1.0: resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + standardwebhooks@1.1.1: + resolution: {integrity: sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==} + tinyexec@1.3.1: resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} engines: {node: '>=18'} @@ -510,6 +543,9 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + typescript@7.0.2: resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} engines: {node: '>=16.20.0'} @@ -578,6 +614,16 @@ snapshots: dependencies: json-schema: 0.4.0 + '@anthropic-ai/sdk@1.3.0(zod@4.6.7)': + dependencies: + '@opentelemetry/api': 1.10.0 + json-schema-to-ts: 3.1.1 + standardwebhooks: 1.1.1 + optionalDependencies: + zod: 4.6.7 + + '@babel/runtime@7.29.10': {} + '@cfworker/json-schema@4.1.1': {} '@google/genai@2.31.0': @@ -591,12 +637,12 @@ snapshots: - supports-color - utf-8-validate - '@langchain/core@1.3.0(openai@7.45.1(google-auth-library@10.9.1)(undici@7.30.1)(ws@8.22.0)(zod@4.6.7))(ws@8.22.0)': + '@langchain/core@1.3.0(@opentelemetry/api@1.10.0)(openai@7.45.1(google-auth-library@10.9.1)(undici@7.30.1)(ws@8.22.0)(zod@4.6.7))(ws@8.22.0)': dependencies: '@cfworker/json-schema': 4.1.1 '@standard-schema/spec': 1.1.0 js-tiktoken: 1.0.21 - langsmith: 0.11.4(openai@7.45.1(google-auth-library@10.9.1)(undici@7.30.1)(ws@8.22.0)(zod@4.6.7))(ws@8.22.0) + langsmith: 0.11.4(@opentelemetry/api@1.10.0)(openai@7.45.1(google-auth-library@10.9.1)(undici@7.30.1)(ws@8.22.0)(zod@4.6.7))(ws@8.22.0) mustache: 4.2.0 p-queue: 6.6.2 zod: 4.6.7 @@ -607,6 +653,8 @@ snapshots: - openai - ws + '@opentelemetry/api@1.10.0': {} + '@protobufjs/aspromise@1.1.2': {} '@protobufjs/base64@1.1.2': {} @@ -631,6 +679,8 @@ snapshots: dependencies: quansync: 1.0.0 + '@stablelib/base64@1.0.1': {} + '@standard-schema/spec@1.1.0': {} '@types/node@26.6.6': @@ -762,6 +812,8 @@ snapshots: dependencies: pure-rand: 8.4.2 + fast-sha256@1.3.0: {} + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: picomatch: 4.0.7 @@ -821,6 +873,11 @@ snapshots: dependencies: bignumber.js: 9.3.1 + json-schema-to-ts@3.1.1: + dependencies: + '@babel/runtime': 7.29.10 + ts-algebra: 2.0.0 + json-schema@0.4.0: {} jsonc-parser@3.3.1: {} @@ -836,10 +893,11 @@ snapshots: jwa: 2.0.1 safe-buffer: 5.2.1 - langsmith@0.11.4(openai@7.45.1(google-auth-library@10.9.1)(undici@7.30.1)(ws@8.22.0)(zod@4.6.7))(ws@8.22.0): + langsmith@0.11.4(@opentelemetry/api@1.10.0)(openai@7.45.1(google-auth-library@10.9.1)(undici@7.30.1)(ws@8.22.0)(zod@4.6.7))(ws@8.22.0): dependencies: p-queue: 6.6.2 optionalDependencies: + '@opentelemetry/api': 1.10.0 openai: 7.45.1(google-auth-library@10.9.1)(undici@7.30.1)(ws@8.22.0)(zod@4.6.7) ws: 8.22.0 @@ -916,6 +974,11 @@ snapshots: secure-json-parse@4.1.0: {} + standardwebhooks@1.1.1: + dependencies: + '@stablelib/base64': 1.0.1 + fast-sha256: 1.3.0 + tinyexec@1.3.1: {} tinyglobby@0.2.17: @@ -923,6 +986,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 + ts-algebra@2.0.0: {} + typescript@7.0.2: optionalDependencies: '@typescript/typescript-aix-ppc64': 7.0.2 diff --git a/tests/anthropic-sdk.ts b/tests/anthropic-sdk.ts new file mode 100644 index 0000000..34c875f --- /dev/null +++ b/tests/anthropic-sdk.ts @@ -0,0 +1,20 @@ +import Anthropic from "@anthropic-ai/sdk"; +import { readStructured } from "streamfold"; +import { anthropic } from "streamfold/anthropic"; + +const client = new Anthropic({ apiKey: "typecheck-only" }); +const request = { + model: "fixture", max_tokens: 32, + messages: [{ role: "user" as const, content: "Weather in Paris?" }], + stream: true as const, +}; +const events = await client.messages.create(request); +readStructured(events, { integration: anthropic }); +const stream = anthropic(); +for await (const event of events) stream.pushAll(event); + +const betaEvents = await client.beta.messages.create(request); +readStructured(betaEvents, { integration: anthropic }); +const betaStream = anthropic(); +for await (const event of betaEvents) betaStream.push(event); +stream.pushAll({ type: "provider_metadata", metadata: { region: "test" } });