From 664aef44a729e5933c726d561fa7e43b4c8c98a3 Mon Sep 17 00:00:00 2001 From: elpresidank Date: Thu, 4 Jun 2026 05:38:08 -0500 Subject: [PATCH] Use Match for Effect AI stream parts --- ts/EFFECT_NATIVE_REWRITE_AUDIT.md | 21 +++++++++++++-- .../__tests__/text-completion-common.test.ts | 3 ++- .../flow/src/model/text-completion/common.ts | 27 +++++++++---------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/ts/EFFECT_NATIVE_REWRITE_AUDIT.md b/ts/EFFECT_NATIVE_REWRITE_AUDIT.md index 29647fa2..067c2c2e 100644 --- a/ts/EFFECT_NATIVE_REWRITE_AUDIT.md +++ b/ts/EFFECT_NATIVE_REWRITE_AUDIT.md @@ -433,6 +433,22 @@ Notes: - `bun run --cwd ts/packages/flow test -- src/__tests__/gateway-dispatcher.test.ts` - `cd ts && bun run check:tsgo` +### 2026-06-04: Effect AI Stream Part Match Slice + +- Status: migrated and package-verified. +- Completed: + - `ts/packages/flow/src/model/text-completion/common.ts` now maps + `Response.StreamPart` values with `effect/Match` instead of a native + `switch`. + - The matcher handles `text-delta`, `finish`, and `error` explicitly, while + preserving ignored behavior for other valid stream parts with + `Match.orElse`. + - Text-completion common tests now include an ignored `text-start` stream + part before text deltas to prove the fallback path remains silent. +- Verification: + - `bun run --cwd ts/packages/flow test -- src/__tests__/text-completion-common.test.ts` + - `cd ts && bun run check:tsgo` + ### 2026-06-02: RAG And Agent Requestor Bridge Slice - Status: migrated, root-verified, committed, and pushed. @@ -1862,8 +1878,9 @@ Notes: installed client, so do not reopen it as an `acquireRelease` close slice without new SDK evidence. - Shared text-completion stream iteration and the Mistral content assertion are - complete. The remaining provider-layer item is parity-backed Effect AI - adapter work, not a direct SDK swap. + complete, and the Effect AI stream-part adapter now uses `effect/Match`. + The remaining provider-layer item is parity-backed Effect AI adapter work, + not a direct SDK swap. - Scratch-note follow-ups: - `Term` / compact client term serialization is complete for base schema, gateway translation, and pure term helper switches. Future work should diff --git a/ts/packages/flow/src/__tests__/text-completion-common.test.ts b/ts/packages/flow/src/__tests__/text-completion-common.test.ts index 8cc41e10..1c4a94af 100644 --- a/ts/packages/flow/src/__tests__/text-completion-common.test.ts +++ b/ts/packages/flow/src/__tests__/text-completion-common.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "@effect/vitest"; import type { LlmChunk } from "@trustgraph/base"; import { Effect, Layer, ManagedRuntime, Stream } from "effect"; -import { AiError, LanguageModel } from "effect/unstable/ai"; +import { AiError, LanguageModel, Response } from "effect/unstable/ai"; import { llmStreamPart, makeLanguageModelProvider, @@ -157,6 +157,7 @@ describe("text completion common helpers", () => { ]), streamText: () => Stream.fromArray([ + Response.makePart("text-start", { id: "part-1" }), { type: "text-delta", id: "part-1", delta: "hel" }, { type: "text-delta", id: "part-1", delta: "lo" }, finishPart(13, 8), diff --git a/ts/packages/flow/src/model/text-completion/common.ts b/ts/packages/flow/src/model/text-completion/common.ts index 88d0dc44..93ca4f22 100644 --- a/ts/packages/flow/src/model/text-completion/common.ts +++ b/ts/packages/flow/src/model/text-completion/common.ts @@ -7,7 +7,7 @@ import { type LlmResult, type LlmProvider, } from "@trustgraph/base"; -import { Config, Effect, Layer, ManagedRuntime, Ref, Result, Stream } from "effect"; +import { Config, Effect, Layer, ManagedRuntime, Match, Ref, Result, Stream } from "effect"; import * as O from "effect/Option"; import * as Predicate from "effect/Predicate"; import * as S from "effect/Schema"; @@ -258,29 +258,26 @@ const languageModelStreamChunk = ( provider: string, model: string, part: Response.StreamPart<{}>, -): Effect.Effect, TextCompletionRuntimeError> => { - switch (part.type) { - case "text-delta": - return Effect.succeed( +): Effect.Effect, TextCompletionRuntimeError> => + Match.value(part).pipe( + Match.discriminators("type")({ + "text-delta": (part) => Effect.succeed( part.delta.length > 0 ? Result.succeed(textChunk(model, part.delta)) : Result.fail(undefined), - ); - case "finish": - return Effect.succeed( + ), + finish: (part) => Effect.succeed( Result.succeed( finalChunk(model, { inToken: usageInputTokens(part.usage), outToken: usageOutputTokens(part.usage), }), ), - ); - case "error": - return Effect.fail(effectAiProviderError(provider, part.error)); - default: - return Effect.succeed(Result.fail(undefined)); - } -}; + ), + error: (part) => Effect.fail(effectAiProviderError(provider, part.error)), + }), + Match.orElse(() => Effect.succeed(Result.fail(undefined))), + ); const runLanguageModelStream = ( runtime: ManagedRuntime.ManagedRuntime,