@effect-uai/anthropic 0.17.0 → 0.19.0-dev-20261109183959

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  //#region \0rolldown/runtime.js
2
2
  var __defProp = Object.defineProperty;
3
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
3
4
  var __exportAll = (all, no_symbols) => {
4
5
  let target = {};
5
6
  for (var name in all) __defProp(target, name, {
@@ -10,4 +11,4 @@ var __exportAll = (all, no_symbols) => {
10
11
  return target;
11
12
  };
12
13
  //#endregion
13
- export { __exportAll as t };
14
+ export { __exportAll as n, __commonJSMin as t };
@@ -1,4 +1,4 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
1
+ import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
2
  import { WireContentBlock, appendInputJsonDelta, appendSignatureDelta, appendTextDelta, appendThinkingDelta, mergeUsage, setStopReason, startBlock } from "./codec.mjs";
3
3
  import { Match, Schema } from "effect";
4
4
  //#region src/streamEvents.ts
@@ -1,6 +1,6 @@
1
1
  import { emptyAccumulator, mergeUsage } from "./codec.mjs";
2
2
  import { applyEvent } from "./streamEvents.mjs";
3
- import { n as describe, r as it, t as globalExpect } from "./test.DNmyFkvJ-N3QqMRwZ.mjs";
3
+ import { n as describe, r as it, t as globalExpect } from "./index.C4tKZ0yW-xEu55bU4.mjs";
4
4
  //#region src/usage.test.ts
5
5
  const wire = {
6
6
  input_tokens: 94,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-uai/anthropic",
3
- "version": "0.17.0",
3
+ "version": "0.19.0-dev-20261109183959",
4
4
  "description": "Anthropic Messages API provider for @effect-uai/core.",
5
5
  "keywords": [
6
6
  "ai",
@@ -43,13 +43,13 @@
43
43
  "access": "public"
44
44
  },
45
45
  "devDependencies": {
46
- "effect": "4.0.0-rc.111",
46
+ "effect": "4.1.2",
47
47
  "typescript": "^7.0.2",
48
- "@effect-uai/core": "0.17.0"
48
+ "@effect-uai/core": "0.19.0-dev-20261109183959"
49
49
  },
50
50
  "peerDependencies": {
51
- "@effect-uai/core": ">=0.2.0 <1",
52
- "effect": ">=4.0.0-rc.111 <5.0.0"
51
+ "@effect-uai/core": "0.19.0-dev-20261109183959",
52
+ "effect": ">=4.0.1 <5.0.0"
53
53
  },
54
54
  "scripts": {
55
55
  "build": "tsdown",
package/src/Anthropic.ts CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  Stream,
11
11
  pipe,
12
12
  } from "effect"
13
- import { HttpClient, HttpClientRequest } from "effect/unstable/http"
13
+ import { HttpClient, HttpClientRequest } from "effect/http"
14
14
  import * as AiError from "@effect-uai/core/AiError"
15
15
  import * as Capabilities from "@effect-uai/core/Capabilities"
16
16
  import {
@@ -10,6 +10,7 @@ const localTool = Tool.make({
10
10
  name: "get_weather",
11
11
  description: "Look up the weather.",
12
12
  inputSchema: Tool.fromEffectSchema(Schema.Struct({ city: Schema.String })),
13
+ outputSchema: Schema.String,
13
14
  run: ({ city }) => Effect.succeed(`sunny in ${city}`),
14
15
  })
15
16
 
@@ -0,0 +1,78 @@
1
+ import { fileBase64, fileUrl } from "@effect-uai/core/File"
2
+ import { imageBytes } from "@effect-uai/core/Image"
3
+ import * as Items from "@effect-uai/core/Items"
4
+ import { Option, Result } from "effect"
5
+ import { describe, expect, it } from "vitest"
6
+ import { buildRequestBody } from "./codec.js"
7
+
8
+ const toolResultFor = (output: Items.ToolCallOutput) => {
9
+ const body = Result.getOrThrow(
10
+ buildRequestBody({
11
+ model: "claude-sonnet-4-5",
12
+ history: [
13
+ Items.userText("go"),
14
+ { type: "function_call", call_id: "c1", name: "shot", arguments: "{}" },
15
+ output,
16
+ ],
17
+ maxTokens: 1024,
18
+ temperature: Option.none(),
19
+ topP: Option.none(),
20
+ topK: Option.none(),
21
+ stopSequences: Option.none(),
22
+ thinking: Option.none(),
23
+ tools: Option.none(),
24
+ toolChoice: Option.none(),
25
+ userId: Option.none(),
26
+ outputConfig: Option.none(),
27
+ cacheControl: Option.none(),
28
+ }),
29
+ )
30
+ return body.messages.at(-1)?.content
31
+ }
32
+
33
+ const outputOf = (output: Items.ToolCallOutput["output"], isError?: boolean) =>
34
+ toolResultFor({
35
+ type: "function_call_output",
36
+ call_id: "c1",
37
+ output,
38
+ ...(isError !== undefined && { isError }),
39
+ })
40
+
41
+ describe("tool_result", () => {
42
+ it("sends text and JSON as one string and flags failures with is_error", () => {
43
+ expect(outputOf([Items.json({ _tag: "CityNotFound", city: "Atlantis" })], true)).toEqual([
44
+ {
45
+ type: "tool_result",
46
+ tool_use_id: "c1",
47
+ content: '{"_tag":"CityNotFound","city":"Atlantis"}',
48
+ is_error: true,
49
+ },
50
+ ])
51
+ })
52
+
53
+ it("sends media as image and document blocks, the filename as the document title", () => {
54
+ expect(
55
+ outputOf([
56
+ Items.text("Home"),
57
+ Items.image(imageBytes(new Uint8Array([1, 2, 3]), "image/png")),
58
+ Items.file(fileBase64("JVBE", "application/pdf"), "report.pdf"),
59
+ Items.file(fileUrl("https://x/a.pdf")),
60
+ ]),
61
+ ).toEqual([
62
+ {
63
+ type: "tool_result",
64
+ tool_use_id: "c1",
65
+ content: [
66
+ { type: "text", text: "Home" },
67
+ { type: "image", source: { type: "base64", media_type: "image/png", data: "AQID" } },
68
+ {
69
+ type: "document",
70
+ source: { type: "base64", media_type: "application/pdf", data: "JVBE" },
71
+ title: "report.pdf",
72
+ },
73
+ { type: "document", source: { type: "url", url: "https://x/a.pdf" } },
74
+ ],
75
+ },
76
+ ])
77
+ })
78
+ })
package/src/codec.ts CHANGED
@@ -1,6 +1,8 @@
1
- import { Array as Arr, Encoding, Match, Option, Order, Result, Schema, pipe } from "effect"
1
+ import { Array as Arr, Match, Option, Order, Result, Schema, pipe } from "effect"
2
+ import { Base64 } from "effect/encoding"
2
3
  import * as Items from "@effect-uai/core/Items"
3
4
  import { JsonParseError } from "@effect-uai/core/JSONL"
5
+ import * as ToolOutput from "@effect-uai/core/ToolOutput"
4
6
  import type { Turn } from "@effect-uai/core/Turn"
5
7
 
6
8
  // ---------------------------------------------------------------------------
@@ -59,9 +61,20 @@ type RequestTextContent = {
59
61
  type RequestToolResultContent = {
60
62
  readonly type: "tool_result"
61
63
  readonly tool_use_id: string
62
- readonly content: string
64
+ readonly content: string | ReadonlyArray<RequestToolResultBlock>
65
+ readonly is_error?: boolean
63
66
  }
64
67
 
68
+ type RequestDocumentContent = {
69
+ readonly type: "document"
70
+ readonly source:
71
+ | { readonly type: "url"; readonly url: string }
72
+ | { readonly type: "base64"; readonly media_type: string; readonly data: string }
73
+ readonly title?: string
74
+ }
75
+
76
+ type RequestToolResultBlock = RequestTextContent | RequestImageContent | RequestDocumentContent
77
+
65
78
  type RequestToolUseContent = {
66
79
  readonly type: "tool_use"
67
80
  readonly id: string
@@ -129,11 +142,50 @@ const imageSourceToWire = Match.type<Items.InputImage["source"]>().pipe(
129
142
  Match.tag("bytes", (s): RequestImageContent["source"] => ({
130
143
  type: "base64",
131
144
  media_type: s.mimeType,
132
- data: Encoding.encodeBase64(s.bytes),
145
+ data: Base64.encode(s.bytes),
146
+ })),
147
+ Match.exhaustive,
148
+ )
149
+
150
+ const fileSourceToWire = Match.type<Items.InputFile["source"]>().pipe(
151
+ Match.tag("url", (s): RequestDocumentContent["source"] => ({ type: "url", url: s.url })),
152
+ Match.tag("base64", (s): RequestDocumentContent["source"] => ({
153
+ type: "base64",
154
+ media_type: s.mimeType,
155
+ data: s.base64,
156
+ })),
157
+ Match.tag("bytes", (s): RequestDocumentContent["source"] => ({
158
+ type: "base64",
159
+ media_type: s.mimeType,
160
+ data: Base64.encode(s.bytes),
133
161
  })),
134
162
  Match.exhaustive,
135
163
  )
136
164
 
165
+ const toolResultBlock = Match.type<Items.ToolOutputContent>().pipe(
166
+ Match.discriminatorsExhaustive("type")({
167
+ input_text: (p): RequestToolResultBlock => ({ type: "text", text: ToolOutput.partText(p) }),
168
+ input_json: (p): RequestToolResultBlock => ({ type: "text", text: ToolOutput.partText(p) }),
169
+ input_image: (p): RequestToolResultBlock => ({
170
+ type: "image",
171
+ source: imageSourceToWire(p.source),
172
+ }),
173
+ input_file: (p): RequestToolResultBlock => ({
174
+ type: "document",
175
+ source: fileSourceToWire(p.source),
176
+ ...(p.filename !== undefined && { title: p.filename }),
177
+ }),
178
+ }),
179
+ )
180
+
181
+ // Text and JSON only: one string. With media: image and document blocks.
182
+ const toolResult = (o: Items.ToolCallOutput): RequestToolResultContent => ({
183
+ type: "tool_result",
184
+ tool_use_id: o.call_id,
185
+ content: Option.getOrElse(ToolOutput.text(o.output), () => o.output.map(toolResultBlock)),
186
+ ...(o.isError === true && { is_error: true }),
187
+ })
188
+
137
189
  const userContentBlock = (
138
190
  block: Items.ContentBlock,
139
191
  ): Result.Result<RequestUserContentBlock, void> =>
@@ -178,9 +230,7 @@ const itemToUserBlocks = (item: Items.HistoryItem): ReadonlyArray<RequestUserCon
178
230
  message: (m): ReadonlyArray<RequestUserContentBlock> =>
179
231
  m.role === "user" ? pipe(m.content, Arr.filterMap(userContentBlock)) : [],
180
232
  function_call: (): ReadonlyArray<RequestUserContentBlock> => [],
181
- function_call_output: (o): ReadonlyArray<RequestUserContentBlock> => [
182
- { type: "tool_result", tool_use_id: o.call_id, content: o.output },
183
- ],
233
+ function_call_output: (o): ReadonlyArray<RequestUserContentBlock> => [toolResult(o)],
184
234
  reasoning: (): ReadonlyArray<RequestUserContentBlock> => [],
185
235
  }),
186
236
  )