Use Match for Effect AI stream parts

This commit is contained in:
elpresidank 2026-06-04 05:38:08 -05:00
parent 3a256096f8
commit 664aef44a7
3 changed files with 33 additions and 18 deletions

View file

@ -433,6 +433,22 @@ Notes:
- `bun run --cwd ts/packages/flow test -- src/__tests__/gateway-dispatcher.test.ts` - `bun run --cwd ts/packages/flow test -- src/__tests__/gateway-dispatcher.test.ts`
- `cd ts && bun run check:tsgo` - `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 ### 2026-06-02: RAG And Agent Requestor Bridge Slice
- Status: migrated, root-verified, committed, and pushed. - Status: migrated, root-verified, committed, and pushed.
@ -1862,8 +1878,9 @@ Notes:
installed client, so do not reopen it as an `acquireRelease` close slice installed client, so do not reopen it as an `acquireRelease` close slice
without new SDK evidence. without new SDK evidence.
- Shared text-completion stream iteration and the Mistral content assertion are - Shared text-completion stream iteration and the Mistral content assertion are
complete. The remaining provider-layer item is parity-backed Effect AI complete, and the Effect AI stream-part adapter now uses `effect/Match`.
adapter work, not a direct SDK swap. The remaining provider-layer item is parity-backed Effect AI adapter work,
not a direct SDK swap.
- Scratch-note follow-ups: - Scratch-note follow-ups:
- `Term` / compact client term serialization is complete for base schema, - `Term` / compact client term serialization is complete for base schema,
gateway translation, and pure term helper switches. Future work should gateway translation, and pure term helper switches. Future work should

View file

@ -1,7 +1,7 @@
import { describe, expect, it } from "@effect/vitest"; import { describe, expect, it } from "@effect/vitest";
import type { LlmChunk } from "@trustgraph/base"; import type { LlmChunk } from "@trustgraph/base";
import { Effect, Layer, ManagedRuntime, Stream } from "effect"; import { Effect, Layer, ManagedRuntime, Stream } from "effect";
import { AiError, LanguageModel } from "effect/unstable/ai"; import { AiError, LanguageModel, Response } from "effect/unstable/ai";
import { import {
llmStreamPart, llmStreamPart,
makeLanguageModelProvider, makeLanguageModelProvider,
@ -157,6 +157,7 @@ describe("text completion common helpers", () => {
]), ]),
streamText: () => streamText: () =>
Stream.fromArray([ Stream.fromArray([
Response.makePart("text-start", { id: "part-1" }),
{ type: "text-delta", id: "part-1", delta: "hel" }, { type: "text-delta", id: "part-1", delta: "hel" },
{ type: "text-delta", id: "part-1", delta: "lo" }, { type: "text-delta", id: "part-1", delta: "lo" },
finishPart(13, 8), finishPart(13, 8),

View file

@ -7,7 +7,7 @@ import {
type LlmResult, type LlmResult,
type LlmProvider, type LlmProvider,
} from "@trustgraph/base"; } 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 O from "effect/Option";
import * as Predicate from "effect/Predicate"; import * as Predicate from "effect/Predicate";
import * as S from "effect/Schema"; import * as S from "effect/Schema";
@ -258,29 +258,26 @@ const languageModelStreamChunk = (
provider: string, provider: string,
model: string, model: string,
part: Response.StreamPart<{}>, part: Response.StreamPart<{}>,
): Effect.Effect<Result.Result<LlmChunk, undefined>, TextCompletionRuntimeError> => { ): Effect.Effect<Result.Result<LlmChunk, undefined>, TextCompletionRuntimeError> =>
switch (part.type) { Match.value(part).pipe(
case "text-delta": Match.discriminators("type")({
return Effect.succeed( "text-delta": (part) => Effect.succeed(
part.delta.length > 0 part.delta.length > 0
? Result.succeed(textChunk(model, part.delta)) ? Result.succeed(textChunk(model, part.delta))
: Result.fail(undefined), : Result.fail(undefined),
); ),
case "finish": finish: (part) => Effect.succeed(
return Effect.succeed(
Result.succeed( Result.succeed(
finalChunk(model, { finalChunk(model, {
inToken: usageInputTokens(part.usage), inToken: usageInputTokens(part.usage),
outToken: usageOutputTokens(part.usage), outToken: usageOutputTokens(part.usage),
}), }),
), ),
); ),
case "error": error: (part) => Effect.fail(effectAiProviderError(provider, part.error)),
return Effect.fail(effectAiProviderError(provider, part.error)); }),
default: Match.orElse(() => Effect.succeed(Result.fail(undefined))),
return Effect.succeed(Result.fail(undefined)); );
}
};
const runLanguageModelStream = <RuntimeRequirements, StreamRequirements extends RuntimeRequirements>( const runLanguageModelStream = <RuntimeRequirements, StreamRequirements extends RuntimeRequirements>(
runtime: ManagedRuntime.ManagedRuntime<RuntimeRequirements, TextCompletionRuntimeError>, runtime: ManagedRuntime.ManagedRuntime<RuntimeRequirements, TextCompletionRuntimeError>,