From 42693c8ca2163cb9ad58e43b8ccda446b8950a55 Mon Sep 17 00:00:00 2001 From: Kinfe123 Date: Thu, 29 Oct 2026 19:13:58 -0700 Subject: [PATCH] feat(cohere): stream v2 tool arguments --- API.md | 9 + CHANGELOG.md | 1 + README.md | 1 + benchmarks/cohere-conformance.test.mjs | 28 +++ examples/tsconfig.json | 2 +- package.json | 1 + packages/core/package.json | 4 + packages/core/src/cohere.d.ts | 30 +++ packages/core/src/cohere.js | 47 +++++ packages/core/src/cohere.test.js | 44 +++++ pnpm-lock.yaml | 263 +++++++++++++++++++++++++ tests/cohere-sdk.ts | 9 + 12 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 benchmarks/cohere-conformance.test.mjs create mode 100644 packages/core/src/cohere.d.ts create mode 100644 packages/core/src/cohere.js create mode 100644 packages/core/src/cohere.test.js create mode 100644 tests/cohere-sdk.ts diff --git a/API.md b/API.md index ff66ee4..7ca53b0 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/gemini` | `gemini` | | `streamfold/langchain` | `langchain` | | `streamfold/ag-ui` | `agUI` | @@ -371,6 +372,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. + ### Errors and diagnostics ```ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e00533..e070587 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. - Reuse the final frozen snapshot in immutable pool completions, avoiding a second JSON value tree while keeping live final values independently parsed. - Release consumed completion payloads in managed SDK readers while preserving diff --git a/README.md b/README.md index cf150ef..6ec168c 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/gemini` | Gemini Interactions | | `streamfold/langchain` | LangChain message chunks | | `streamfold/ag-ui` | AG-UI events | 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 fe54bd6..2a09849 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"] + "files": ["assistant-ui.mjs", "vercel-ai.mjs", "custom.mjs", "../tests/langchain-sdk.ts", "../tests/cohere-sdk.ts"] } diff --git a/package.json b/package.json index a47a2e8..3667aa5 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "ai": "7.0.152", "assistant-stream": "0.3.42", "bumpp": "^12.3.0", + "cohere-ai": "8.3.0", "fast-check": "4.10.2", "playwright": "^1.63.0", "streamfold": "workspace:*", diff --git a/packages/core/package.json b/packages/core/package.json index 03d6158..22f0d15 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -28,6 +28,10 @@ "types": "./src/anthropic.d.ts", "default": "./src/anthropic.js" }, + "./cohere": { + "types": "./src/cohere.d.ts", + "default": "./src/cohere.js" + }, "./assistant-ui": { "types": "./src/assistant-ui.d.ts", "default": "./src/assistant-ui.js" 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 7381c13..5f22cff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: bumpp: specifier: ^12.3.0 version: 12.3.0 + cohere-ai: + specifier: 8.3.0 + version: 8.3.0 fast-check: specifier: 4.10.2 version: 4.10.2 @@ -196,6 +199,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'} + ai@7.0.152: resolution: {integrity: sha512-cxQaE/17qhgqJcLCespbnyaN3c4IkGuNUtBXKSX1yTvlSsQ7ERV2ygFADMG7d+IWCUuvi7R8DrqAy9DGj0cgkg==} engines: {node: '>=22'} @@ -216,9 +223,15 @@ 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==} + 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} @@ -228,12 +241,70 @@ 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'} + 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'} + + 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'} @@ -251,6 +322,48 @@ packages: picomatch: optional: true + 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'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + 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'} + + 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'} + + 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 @@ -284,6 +397,18 @@ packages: ws: optional: true + 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'} + mustache@4.2.0: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} hasBin: true @@ -322,15 +447,29 @@ packages: engines: {node: '>=20'} hasBin: true + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + pure-rand@8.4.2: resolution: {integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==} 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} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + secure-json-parse@4.1.0: resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + 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'} @@ -476,6 +615,10 @@ snapshots: '@workflow/serde@4.1.0': {} + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + ai@7.0.152(zod@4.6.7): dependencies: '@ai-sdk/gateway': 4.0.125(zod@4.6.7) @@ -491,8 +634,15 @@ snapshots: nanoid: 6.0.1 secure-json-parse: 4.1.0 + asynckit@0.4.0: {} + base64-js@1.5.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 @@ -507,10 +657,53 @@ 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: + dependencies: + form-data: 4.0.6 + form-data-encoder: 4.1.0 + formdata-node: 6.0.3 + readable-stream: 4.8.0 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + 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 + + 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: {} fast-check@4.10.2: @@ -521,6 +714,52 @@ snapshots: optionalDependencies: picomatch: 4.0.7 + 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: {} + + function-bind@1.1.2: {} + + 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 + + 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 + + ieee754@1.2.1: {} + jiti@2.7.0: {} js-tiktoken@1.0.21: @@ -535,6 +774,14 @@ snapshots: dependencies: p-queue: 6.6.2 + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mustache@4.2.0: {} nanoid@6.0.1: {} @@ -560,12 +807,28 @@ snapshots: dependencies: playwright-core: 1.63.0 + process@0.11.10: {} + pure-rand@8.4.2: {} 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 + + safe-buffer@5.2.1: {} + secure-json-parse@4.1.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);