diff --git a/API.md b/API.md index 69a4eaf..d83e077 100644 --- a/API.md +++ b/API.md @@ -332,6 +332,7 @@ composable integration value: | `streamfold/vercel-ai` | `vercelAI` | | `streamfold/openai` | `openAI` | | `streamfold/anthropic` | `anthropic` | +| `streamfold/cohere` | `cohere` | | `streamfold/bedrock` | `bedrock` | | `streamfold/gemini` | `gemini` | | `streamfold/langchain` | `langchain` | @@ -380,6 +381,14 @@ boundaries, `tool-input-end` continues to complete a call immediately. `tool-call` and UI `tool-input-available` events also work without preceding deltas. Repeated completion events do not emit duplicate results. +For Cohere v2, pass `client.chatStream(...)` from `CohereClientV2` to +`readStructured` with `integration: cohere`. Both the SDK's camelCase +`toolCalls` payloads and decoded wire `tool_calls` payloads are accepted. +Calls are correlated by index and expose the tool-call ID. They finish at +`tool-call-end` or EOF. Text and tool-plan events are ignored; malformed JSON +and generation error messages fail the integration. The Cohere SDK is not +a runtime dependency of Streamfold. + For Amazon Bedrock, pass the `stream` returned by `ConverseStreamCommand` to `readStructured` with `integration: bedrock`. The adapter correlates tool-use blocks by `contentBlockIndex` and exposes `toolUseId` as the call ID. Text, diff --git a/CHANGELOG.md b/CHANGELOG.md index 6859859..94160db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Add a Cohere v2 adapter for SDK and decoded wire tool argument streams. - Add a Bedrock ConverseStream adapter for streaming tool-use arguments. - Accept Anthropic stable and beta SDK message event interfaces directly in the Anthropic adapter and managed reader. diff --git a/README.md b/README.md index addf393..08eb9d4 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Streamfold includes isolated adapters for decoded SDK events: | `streamfold/vercel-ai` | Vercel AI SDK | | `streamfold/openai` | OpenAI Responses | | `streamfold/anthropic` | Anthropic Messages | +| `streamfold/cohere` | Cohere v2 Chat | | `streamfold/bedrock` | Amazon Bedrock ConverseStream | | `streamfold/gemini` | Gemini Interactions | | `streamfold/langchain` | LangChain message chunks | diff --git a/benchmarks/cohere-conformance.test.mjs b/benchmarks/cohere-conformance.test.mjs new file mode 100644 index 0000000..42308f2 --- /dev/null +++ b/benchmarks/cohere-conformance.test.mjs @@ -0,0 +1,28 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { CohereClientV2 } from "cohere-ai"; +import { readStructured } from "../packages/core/src/index.js"; +import { cohere } from "../packages/core/src/cohere.js"; + +test("reads interleaved arguments from Cohere SDK camelCase SSE events", async () => { + const events = [ + { type: "message-start", id: "fixture", delta: { message: { role: "assistant" } } }, + { type: "tool-plan-delta", delta: { message: { tool_plan: "Check both cities" } } }, + ...["paris", "tokyo"].map((id, index) => ({ type: "tool-call-start", index, delta: { message: { tool_calls: { id, type: "function", function: { name: "weather", arguments: "" } } } } })), + { type: "tool-call-delta", index: 0, delta: { message: { tool_calls: { function: { arguments: '{"city":"Pa' } } } } }, + { type: "tool-call-delta", index: 1, delta: { message: { tool_calls: { function: { arguments: '{"city":"Tokyo"}' } } } } }, + { type: "tool-call-delta", index: 0, delta: { message: { tool_calls: { function: { arguments: 'ris"}' } } } } }, + { type: "tool-call-end", index: 1 }, { type: "tool-call-end", index: 0 }, + { type: "message-end", delta: { finish_reason: "TOOL_CALL" } }, + ]; + const client = new CohereClientV2({ + token: "test-token", + fetch: async () => new Response(events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join(""), { headers: { "Content-Type": "text/event-stream" } }), + }); + const source = await client.chatStream({ model: "command-a", messages: [{ role: "user", content: "Weather?" }] }); + const updates = []; + for await (const update of readStructured(source, { integration: cohere, limits: { snapshots: "immutable" } })) updates.push(update); + assert.deepEqual(updates.map(({ type, id }) => [type, id]), [["start", "paris"], ["start", "tokyo"], ["update", "paris"], ["update", "tokyo"], ["update", "paris"], ["complete", "tokyo"], ["complete", "paris"]]); + assert.deepEqual(updates[2].partialValue, { city: "Pa" }); + assert.deepEqual(updates.slice(-2).map(({ value }) => value), [{ city: "Tokyo" }, { city: "Paris" }]); +}); diff --git a/examples/tsconfig.json b/examples/tsconfig.json index 5a01f2e..0a37881 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", "../tests/anthropic-sdk.ts", "../tests/bedrock-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", "../tests/bedrock-sdk.ts", "../tests/cohere-sdk.ts"] } diff --git a/package.json b/package.json index 33e6fdd..eae207a 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "ai": "7.0.152", "assistant-stream": "0.3.42", "bumpp": "^12.3.0", + "cohere-ai": "8.3.0", "fast-check": "4.10.2", "openai": "7.45.1", "playwright": "^1.63.0", diff --git a/packages/core/package.json b/packages/core/package.json index 7d0fe43..1cb0a44 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -51,6 +51,10 @@ "./vercel-ai": { "types": "./src/vercel-ai.d.ts", "default": "./src/vercel-ai.js" + }, + "./cohere": { + "types": "./src/cohere.d.ts", + "default": "./src/cohere.js" } }, "engines": { diff --git a/packages/core/src/cohere.d.ts b/packages/core/src/cohere.d.ts new file mode 100644 index 0000000..79952f4 --- /dev/null +++ b/packages/core/src/cohere.d.ts @@ -0,0 +1,30 @@ +import type { + BatchEventStructuredStream, + BatchStructuredStreamIntegration, + StructuredStreamIntegrationOptions, + StructuredStreamPool, +} from "./index.js"; + +type ToolCall = { + readonly id?: string; + readonly function?: { readonly arguments?: string }; +}; + +export type CohereToolCallEvent = + | { + readonly type: "tool-call-start" | "tool-call-delta"; + readonly index?: number; + readonly delta?: { + readonly message?: { readonly toolCalls?: ToolCall; readonly tool_calls?: ToolCall }; + }; + } + | { readonly type: "tool-call-end"; readonly index?: number } + | { readonly type: string } + | { readonly type: string; readonly [key: string]: unknown }; + +export const cohere: BatchStructuredStreamIntegration; + +export function createStructuredStream( + pool?: StructuredStreamPool, + options?: StructuredStreamIntegrationOptions, +): BatchEventStructuredStream; diff --git a/packages/core/src/cohere.js b/packages/core/src/cohere.js new file mode 100644 index 0000000..0e5e835 --- /dev/null +++ b/packages/core/src/cohere.js @@ -0,0 +1,47 @@ +import { createStructuredStreamPool } from "./index.js"; +import { createIntegration } from "./internal/integration.js"; + +export const cohere = (pool = createStructuredStreamPool(), options) => { + const idsByIndex = new Map(); + return createIntegration( + pool, + (event, calls) => { + if (event.type === "message-end" && event.delta?.error) throw new Error(event.delta.error); + const message = event.delta?.message; + const tool = message?.toolCalls ?? message?.tool_calls; + if (event.type === "tool-call-start") { + if (event.index === undefined) { + calls.unmatched("Tool call start has no index"); + return undefined; + } + const id = tool?.id; + if (id !== undefined) idsByIndex.set(event.index, id); + return calls.start(id, tool?.function?.arguments ?? ""); + } + if (event.type === "tool-call-delta") { + const id = idsByIndex.get(event.index); + if (id === undefined) { + calls.unmatched("Arguments delta has no matching tool call index"); + return undefined; + } + return calls.push(id, tool?.function?.arguments ?? ""); + } + if (event.type === "tool-call-end") { + const id = idsByIndex.get(event.index); + if (id !== undefined) { + idsByIndex.delete(event.index); + return calls.complete(id); + } + } + return undefined; + }, + (complete) => { + for (const id of idsByIndex.values()) complete(id); + idsByIndex.clear(); + }, + "cohere", + options, + ); +}; + +export { cohere as createStructuredStream }; diff --git a/packages/core/src/cohere.test.js b/packages/core/src/cohere.test.js new file mode 100644 index 0000000..42278e6 --- /dev/null +++ b/packages/core/src/cohere.test.js @@ -0,0 +1,44 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { createStructuredStreamPool } from "./index.js"; +import { cohere } from "./cohere.js"; + +const start = (index, id, args = "") => ({ type: "tool-call-start", index, delta: { message: { tool_calls: { id, function: { arguments: args } } } } }); +const delta = (index, args) => ({ type: "tool-call-delta", index, delta: { message: { tool_calls: { function: { arguments: args } } } } }); +const end = (index) => ({ type: "tool-call-end", index }); + +test("Cohere handles decoded wire arguments and finalizes each call once", () => { + const stream = cohere(); + stream.push(start(0, "first", '{"city":')); + stream.push(delta(0, '"Paris"}')); + assert.deepEqual(stream.pushAll(end(0)).map(({ id, value }) => ({ id, value })), [{ id: "first", value: { city: "Paris" } }]); + assert.deepEqual(stream.pushAll(end(0)), []); + stream.push(start(0, "second", "{}")); + assert.deepEqual(stream.finish().map(({ id, value }) => ({ id, value })), [{ id: "first", value: { city: "Paris" } }, { id: "second", value: {} }]); +}); + +test("Cohere releases active calls on truncated arguments, limits, or provider errors", () => { + for (const [options, input, ending, expected] of [ + [{}, '{"city":', end(0), /complete|incomplete/i], + [{ maxBytes: 4 }, '{"city":"Paris"}', end(0), /maxBytes/], + [{}, '{}', { type: "message-end", delta: { error: "generation failed" } }, /generation failed/], + ]) { + const pool = createStructuredStreamPool(options); + const stream = cohere(pool); + stream.push(start(0, "first")); + stream.push(start(1, "second")); + assert.throws(() => { stream.push(delta(0, input)); stream.push(ending); }, expected); + assert.equal(pool.size, 0); + assert.throws(() => stream.finish(), expected); + } +}); + +test("Cohere ignores text and diagnoses uncorrelated argument events", () => { + const diagnostics = []; + const stream = cohere(undefined, { onDiagnostic: (value) => diagnostics.push(value) }); + assert.deepEqual(stream.pushAll({ type: "content-delta", delta: { message: { content: { text: "Hello" } } } }), []); + stream.push(delta(9, "{}")); + stream.push({ type: "tool-call-start", delta: { message: { tool_calls: { id: "missing-index" } } } }); + assert.deepEqual(stream.finish(), []); + assert.equal(diagnostics.filter(({ code }) => code === "UNMATCHED_TOOL_EVENT").length, 2); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f422b00..88910a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: bumpp: specifier: ^12.3.0 version: 12.3.0 + cohere-ai: + specifier: 8.3.0 + version: 8.3.0(@smithy/signature-v4@5.7.4) fast-check: specifier: 4.10.2 version: 4.10.2 @@ -370,6 +373,10 @@ packages: '@workflow/serde@4.1.0': resolution: {integrity: sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -394,6 +401,9 @@ packages: redis: optional: true + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -406,6 +416,9 @@ packages: buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + bumpp@12.3.0: resolution: {integrity: sha512-YgW09sgbIGQmAD/Jk7FEGP0tm0PeDz+Zgk+IAk+MOHhb/98pj/l3CgkxSjd7C1O99JPxTNwS+bPOTNaHuNh6lQ==} engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} @@ -415,6 +428,32 @@ packages: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + cohere-ai@8.3.0: + resolution: {integrity: sha512-3EIpXefkHcjYgbPDxwJDPa+XgHRSotD1YNi+HrROtuezLLQ12seKZA2GBJSHllbOEBhv+j/TY+f1BGUBn9w0SQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@aws-crypto/sha256-js': ^5.2.0 + '@aws-sdk/credential-providers': ^3.583.0 + '@smithy/protocol-http': ^5.1.2 + '@smithy/signature-v4': ^5.1.2 + peerDependenciesMeta: + '@aws-crypto/sha256-js': + optional: true + '@aws-sdk/credential-providers': + optional: true + '@smithy/protocol-http': + optional: true + '@smithy/signature-v4': + optional: true + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} @@ -431,12 +470,44 @@ packages: defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + eventsource-parser@3.1.1: resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} engines: {node: '>=18.0.0'} @@ -464,10 +535,25 @@ packages: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} + form-data-encoder@4.1.0: + resolution: {integrity: sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==} + engines: {node: '>= 18'} + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + formdata-node@6.0.3: + resolution: {integrity: sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==} + engines: {node: '>= 18'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gaxios@7.3.1: resolution: {integrity: sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==} engines: {node: '>=18'} @@ -476,6 +562,14 @@ packages: resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} engines: {node: '>=18'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + google-auth-library@10.9.1: resolution: {integrity: sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw==} engines: {node: '>=18'} @@ -484,10 +578,29 @@ packages: resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} engines: {node: '>=14'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true @@ -537,6 +650,18 @@ packages: long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -618,6 +743,10 @@ packages: engines: {node: '>=20'} hasBin: true + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + protobufjs@7.6.6: resolution: {integrity: sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==} engines: {node: '>=12.0.0'} @@ -628,6 +757,10 @@ packages: quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + readable-stream@4.8.0: + resolution: {integrity: sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -641,6 +774,9 @@ packages: standardwebhooks@1.1.1: resolution: {integrity: sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + tinyexec@1.3.1: resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} engines: {node: '>=18'} @@ -1068,6 +1204,10 @@ snapshots: '@workflow/serde@4.1.0': {} + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + agent-base@7.1.4: {} ai@7.0.152(zod@4.6.7): @@ -1085,6 +1225,8 @@ snapshots: nanoid: 6.0.1 secure-json-parse: 4.1.0 + asynckit@0.4.0: {} + base64-js@1.5.1: {} bignumber.js@9.3.1: {} @@ -1093,6 +1235,11 @@ snapshots: buffer-equal-constant-time@1.0.1: {} + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + bumpp@12.3.0: dependencies: args-tokenizer: 0.3.0 @@ -1107,6 +1254,24 @@ snapshots: cac@7.0.0: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + cohere-ai@8.3.0(@smithy/signature-v4@5.7.4): + dependencies: + form-data: 4.0.6 + form-data-encoder: 4.1.0 + formdata-node: 6.0.3 + readable-stream: 4.8.0 + optionalDependencies: + '@smithy/signature-v4': 5.7.4 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + data-uri-to-buffer@4.0.1: {} debug@4.4.3: @@ -1115,12 +1280,39 @@ snapshots: defu@6.1.7: {} + delayed-stream@1.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + event-target-shim@5.0.1: {} + eventemitter3@4.0.7: {} + events@3.3.0: {} + eventsource-parser@3.1.1: {} extend@3.0.2: {} @@ -1140,10 +1332,24 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 + form-data-encoder@4.1.0: {} + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + formdata-node@6.0.3: {} + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 + function-bind@1.1.2: {} + gaxios@7.3.1: dependencies: extend: 3.0.2 @@ -1160,6 +1366,24 @@ snapshots: transitivePeerDependencies: - supports-color + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + google-auth-library@10.9.1: dependencies: base64-js: 1.5.1 @@ -1173,6 +1397,18 @@ snapshots: google-logging-utils@1.1.3: {} + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -1180,6 +1416,8 @@ snapshots: transitivePeerDependencies: - supports-color + ieee754@1.2.1: {} + jiti@2.7.0: {} js-tiktoken@1.0.21: @@ -1220,6 +1458,14 @@ snapshots: long@5.3.2: {} + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + ms@2.1.3: {} mustache@4.2.0: {} @@ -1269,6 +1515,8 @@ snapshots: dependencies: playwright-core: 1.63.0 + process@0.11.10: {} + protobufjs@7.6.6: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -1287,6 +1535,14 @@ snapshots: quansync@1.0.0: {} + readable-stream@4.8.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + retry@0.13.1: {} safe-buffer@5.2.1: {} @@ -1298,6 +1554,10 @@ snapshots: '@stablelib/base64': 1.0.1 fast-sha256: 1.3.0 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + tinyexec@1.3.1: {} tinyglobby@0.2.17: diff --git a/tests/cohere-sdk.ts b/tests/cohere-sdk.ts new file mode 100644 index 0000000..c5cd936 --- /dev/null +++ b/tests/cohere-sdk.ts @@ -0,0 +1,9 @@ +import { CohereClientV2 } from "cohere-ai"; +import { readStructured } from "streamfold"; +import { cohere } from "streamfold/cohere"; + +const client = new CohereClientV2({ token: "typecheck-only" }); +const events = await client.chatStream({ model: "command-a", messages: [{ role: "user", content: "Weather?" }] }); +readStructured(events, { integration: cohere }); +const stream = cohere(); +for await (const event of events) stream.pushAll(event);