From c201f2a046a6c5cf95e863102390ba5c440219e0 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Thu, 29 Oct 2026 07:50:47 +0000 Subject: [PATCH] chore: bump effect to 4.0.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump the exact effect devDependency pin in packages/effect from 4.0.6 to 4.0.7. effect 4.0.7 was published 2026-10-28T03:36:20Z, about 28 hours before the bump (2026-10-29T07:47Z), and is the newest 4.0.x with at least 24 hours of publish age. effect 4.1.0 (2026-10-29T04:09Z) is too new and outside the peer range. The peer range stays ~4.0.0. The lockfile still holds one effect (4.0.7, no dependencies). effect 4.0.7 adds a toModelOutput option to Tool.make and Tool.dynamic (Effect-TS/effect#9861). effect/ai then sets an optional modelOutput (text and file parts) on successful Prompt tool-result parts, and its own providers send it to the model in place of the serialized result. The bridge ignored the field and sent the JSON result, so a tool's images and files never reached the model. toolResultToV4 now maps modelOutput onto the AI SDK "content" tool result output. Failures keep error-json, and parts without modelOutput keep json. No other effect/ai API drift affects the bridge: AiError, LanguageModel and Toolkit .d.ts files are unchanged, and Response adds only the optional modelOutput on tool-result parts plus stricter decoding services for Response.Part (Effect-TS/effect#9873). Signed-off-by: Thomas Kosiewski --- _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_ Change-Id: I866976cbc9f696e9a288fbf48986ace8640fa7cf Signed-off-by: Thomas Kosiewski --- packages/effect/package.json | 2 +- packages/effect/src/language-model.ts | 9 +++ packages/effect/test/language-model.test.ts | 72 +++++++++++++++++++++ pnpm-lock.yaml | 10 +-- 4 files changed, 87 insertions(+), 6 deletions(-) diff --git a/packages/effect/package.json b/packages/effect/package.json index 71b7eed..6b56def 100644 --- a/packages/effect/package.json +++ b/packages/effect/package.json @@ -68,7 +68,7 @@ "@types/node": "^26.6.3", "@vitest/coverage-v8": "^5.0.5", "ai": "^7.0.155", - "effect": "4.0.6", + "effect": "4.0.7", "publint": "^0.3.25", "tsup": "^9.0.0", "tsx": "^4.23.15", diff --git a/packages/effect/src/language-model.ts b/packages/effect/src/language-model.ts index c133129..cf38b2c 100644 --- a/packages/effect/src/language-model.ts +++ b/packages/effect/src/language-model.ts @@ -498,6 +498,15 @@ const toolResultToV4 = (part: Prompt.ToolResultPart): LanguageModelV4ToolResultP let output: LanguageModelV4ToolResultOutput; if (part.isFailure) { output = { type: "error-json", value }; + } else if (part.modelOutput !== undefined) { + // A tool's `toModelOutput` mapping (effect 4.0.7+) replaces the result + // sent to the model. + output = { + type: "content", + value: part.modelOutput.map((item) => + item.type === "text" ? { type: "text", text: item.text } : filePartToV4(item), + ), + }; } else { output = { type: "json", value }; } diff --git a/packages/effect/test/language-model.test.ts b/packages/effect/test/language-model.test.ts index 48dfa81..a0ec75c 100644 --- a/packages/effect/test/language-model.test.ts +++ b/packages/effect/test/language-model.test.ts @@ -178,6 +178,78 @@ describe("generateText", () => { ]); }); + it("sends a tool's model output in place of its serialized result", async () => { + let captured: LanguageModelV4CallOptions | undefined; + const model = fakeModel({ + doGenerate: async (options) => { + captured = options; + return { + content: [{ type: "text", text: "ok" }], + finishReason: { unified: "stop", raw: undefined }, + usage: USAGE, + warnings: [], + }; + }, + }); + + const messages: Array = [ + { role: "user", content: [{ type: "text", text: "Take a screenshot." }] }, + { + role: "assistant", + content: [{ type: "tool-call", id: "call_1", name: "screenshot", params: {} }], + }, + { + role: "tool", + content: [ + { + type: "tool-result", + id: "call_1", + name: "screenshot", + isFailure: false, + result: { path: "/tmp/shot.png" }, + modelOutput: [ + { type: "text", text: "Screenshot" }, + { + type: "file", + mediaType: "image/png", + fileName: "shot.png", + data: "iVBORw0KGgo=", + }, + ], + providerExecuted: false, + }, + ], + }, + ]; + + await Effect.runPromise( + Effect.flatMap(service(model), (m) => m.generateText({ prompt: messages })), + ); + + expect(captured?.prompt.at(-1)).toEqual({ + role: "tool", + content: [ + { + type: "tool-result", + toolCallId: "call_1", + toolName: "screenshot", + output: { + type: "content", + value: [ + { type: "text", text: "Screenshot" }, + { + type: "file", + mediaType: "image/png", + filename: "shot.png", + data: { type: "data", data: "iVBORw0KGgo=" }, + }, + ], + }, + }, + ], + }); + }); + it("sends tool definitions as JSON schema and resolves tool calls", async () => { const GetWeather = Tool.make("get_weather", { description: "Look up the current weather", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 25e48d4..923f746 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -97,8 +97,8 @@ importers: specifier: ^7.0.155 version: 7.0.155(zod@4.6.7) effect: - specifier: 4.0.6 - version: 4.0.6 + specifier: 4.0.7 + version: 4.0.7 publint: specifier: ^0.3.25 version: 0.3.25 @@ -1531,8 +1531,8 @@ packages: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} - effect@4.0.6: - resolution: {integrity: sha512-Tc3mkzuLR6t6P9dhLt0D2GrxYtmU5td4XEJEiXCmILz53IRIfJyHsKZWjTA3xEjiwpNTtXBgI4iqYPxMztBDrw==} + effect@4.0.7: + resolution: {integrity: sha512-47CiUzNAmeLr40pDT9vKMxxtB8o3bdpZEKU4IUM3fVxsK2GYzPsV7fQoZP0I41A308BZd71nMxqJ0KRh3s9u2g==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3373,7 +3373,7 @@ snapshots: dependencies: is-obj: 2.0.0 - effect@4.0.6: {} + effect@4.0.7: {} emoji-regex@8.0.0: {}