@effect-uai/anthropic 0.10.0 → 0.12.0

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,6 +1,7 @@
1
- import { ThinkingConfig } from "./codec.mjs";
1
+ import { CacheTtl, ThinkingConfig } from "./codec.mjs";
2
2
  import { AnthropicModel } from "./models.mjs";
3
3
  import { ProviderEvent } from "./streamEvents.mjs";
4
+ import { UserLocation, WebSearchOptions, codeExecutionTool, webSearchTool } from "./AnthropicTools.mjs";
4
5
  import { Context, Effect, Layer, Redacted, Stream } from "effect";
5
6
  import { HttpClient } from "effect/unstable/http";
6
7
  import * as AiError from "@effect-uai/core/AiError";
@@ -9,7 +10,7 @@ import { Turn, TurnEvent } from "@effect-uai/core/Turn";
9
10
 
10
11
  //#region src/Anthropic.d.ts
11
12
  declare namespace Anthropic_d_exports {
12
- export { Anthropic, AnthropicRequest, AnthropicService, Config, layer, make, toCanonical };
13
+ export { Anthropic, AnthropicRequest, AnthropicService, Config, UserLocation, WebSearchOptions, codeExecutionTool, layer, make, toCanonical, webSearchTool };
13
14
  }
14
15
  type AnthropicRequest = Omit<CommonRequest, "model"> & {
15
16
  /**
@@ -78,6 +79,23 @@ type Config = {
78
79
  * 4096 if neither is set.
79
80
  */
80
81
  readonly defaultMaxTokens?: number;
82
+ /**
83
+ * Prompt caching. Off by default, so the library stays unopinionated about
84
+ * a setting that changes billing. `true` is `{}`: cache with the 5-minute
85
+ * default TTL.
86
+ *
87
+ * Anthropic caches the `tools` → `system` → `messages` prefix and moves the
88
+ * breakpoint forward as the conversation grows, so a long system prompt and
89
+ * tool definitions are paid for once rather than on every turn. Read
90
+ * `usage.input_tokens_details.cached_tokens` to see it working.
91
+ *
92
+ * Mutating earlier history (rewriting an old message, compaction) breaks
93
+ * the prefix from that point on. Anthropic falls back to the longest
94
+ * unchanged prefix, so this costs savings but never correctness.
95
+ */
96
+ readonly promptCaching?: boolean | {
97
+ readonly ttl?: CacheTtl;
98
+ };
81
99
  };
82
100
  /**
83
101
  * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.
@@ -98,5 +116,5 @@ declare const make: (cfg: Config) => Effect.Effect<AnthropicService, never, Http
98
116
  */
99
117
  declare const layer: (cfg: Config) => Layer.Layer<Anthropic | LanguageModel, never, HttpClient.HttpClient>;
100
118
  //#endregion
101
- export { Anthropic, AnthropicRequest, AnthropicService, Config, layer, make, Anthropic_d_exports as t, toCanonical };
119
+ export { Anthropic, AnthropicRequest, AnthropicService, Config, type UserLocation, type WebSearchOptions, codeExecutionTool, layer, make, Anthropic_d_exports as t, toCanonical, webSearchTool };
102
120
  //# sourceMappingURL=Anthropic.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Anthropic.d.mts","names":[],"sources":["../src/Anthropic.ts"],"mappings":";;;;;;;;;;;;;KAqCY,gBAAA,GAAmB,IAAA,CAAK,aAAA;;;;;WAKzB,KAAA,EAAO,cAAA;;;;;WAKP,IAAA;WAEA,aAAA,GAAgB,aAAA;EAZC;;;;;EAAA,SAkBjB,QAAA,GAAW,cAAA;EAAA;;;EAAA,SAIX,IAAA;AAAA;AAAA,KAGC,gBAAA;EApBM;;;;;EAAA,SA0BP,YAAA,GACP,OAAA,EAAS,gBAAA,KACN,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,OAAA,CAAQ,OAAA;EAXjC;;;AAGX;EAHW,SAgBA,UAAA,GAAa,OAAA,EAAS,gBAAA,KAAqB,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,CAAQ,OAAA;EAb3D;;;;;;;EAAA,SAqBjB,IAAA,GAAO,OAAA,EAAS,gBAAA,KAAqB,MAAA,CAAO,MAAA,CAAO,IAAA,EAAM,OAAA,CAAQ,OAAA;EARtB;;;;;EAAA,SAc3C,WAAA,SACP,CAAA,EAAG,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,CAAA,EAAG,CAAA,MAChC,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,CAAA,EAAG,CAAA;AAAA;AAAA,cAClC,cAAA;;;;;;;cAQY,SAAA,SAAkB,cAAA;AAAA,KAInB,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA;EApCG;;;;;EAAA,SA0CH,gBAAA;AAAA;;;;;;cA8NE,WAAA,SACX,CAAA,EAAG,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,CAAA,EAAG,CAAA,MAClC,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,CAAA,EAAG,CAAA;;;;cAmBlB,IAAA,GAAQ,GAAA,EAAK,MAAA,KAAS,MAAA,CAAO,MAAA,CAAO,gBAAA,SAAyB,UAAA,CAAW,UAAA;;;;;;;;cAqBxE,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,SAAA,GAAY,aAAA,SAAsB,UAAA,CAAW,UAAA"}
1
+ {"version":3,"file":"Anthropic.d.mts","names":[],"sources":["../src/Anthropic.ts"],"mappings":";;;;;;;;;;;;;;KA+CY,gBAAA,GAAmB,IAAA,CAAK,aAAA;;;;;WAKzB,KAAA,EAAO,cAAA;;;;;WAKP,IAAA;WAEA,aAAA,GAAgB,aAAA;;;;AAZ3B;;WAkBW,QAAA,GAAW,cAAA;EAlBc;;;EAAA,SAsBzB,IAAA;AAAA;AAAA,KAGC,gBAAA;EAPwB;;;;;EAAA,SAazB,YAAA,GACP,OAAA,EAAS,gBAAA,KACN,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,OAAA,CAAQ,OAAA;EArBjC;;;;EAAA,SA0BA,UAAA,GAAa,OAAA,EAAS,gBAAA,KAAqB,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,CAAQ,OAAA;EAhBxE;;AAGf;;;;;EAHe,SAwBJ,IAAA,GAAO,OAAA,EAAS,gBAAA,KAAqB,MAAA,CAAO,MAAA,CAAO,IAAA,EAAM,OAAA,CAAQ,OAAA;EAbrE;;;;;EAAA,SAmBI,WAAA,SACP,CAAA,EAAG,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,CAAA,EAAG,CAAA,MAChC,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,CAAA,EAAG,CAAA;AAAA;AAAA,cAClC,cAAA;;;;;;;cAQY,SAAA,SAAkB,cAAA;AAAA,KAInB,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA;EAtCA;;;;;EAAA,SA4CA,gBAAA;EA1CyB;;;;;;;;;;;;;;EAAA,SAyDzB,aAAA;IAAA,SAAqC,GAAA,GAAM,QAAA;EAAA;AAAA;;;;;;cAsQzC,WAAA,SACX,CAAA,EAAG,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,CAAA,EAAG,CAAA,MAClC,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,CAAA,EAAG,CAAA;;;;cAmBlB,IAAA,GAAQ,GAAA,EAAK,MAAA,KAAS,MAAA,CAAO,MAAA,CAAO,gBAAA,SAAyB,UAAA,CAAW,UAAA;;;;;;;AA9TpF;cAmVY,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,SAAA,GAAY,aAAA,SAAsB,UAAA,CAAW,UAAA"}
@@ -1,4 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
2
+ import { codeExecutionTool, renderProviderTools, webSearchTool } from "./AnthropicTools.mjs";
2
3
  import { accumulatorToTurn, buildRequestBody, emptyAccumulator } from "./codec.mjs";
3
4
  import { KnownProviderEvent, applyEvent } from "./streamEvents.mjs";
4
5
  import { Context, Effect, Layer, Match, Option, Redacted, Result, Schema, Stream, pipe } from "effect";
@@ -6,14 +7,16 @@ import { HttpClient, HttpClientRequest } from "effect/unstable/http";
6
7
  import * as AiError from "@effect-uai/core/AiError";
7
8
  import { LanguageModel, turnFromStream } from "@effect-uai/core/LanguageModel";
8
9
  import * as SSE from "@effect-uai/core/SSE";
9
- import { descriptorsOf } from "@effect-uai/core/Tool";
10
+ import { descriptorsOf, providerToolsOf } from "@effect-uai/core/Tool";
10
11
  import { TurnEvent } from "@effect-uai/core/Turn";
11
12
  //#region src/Anthropic.ts
12
13
  var Anthropic_exports = /* @__PURE__ */ __exportAll({
13
14
  Anthropic: () => Anthropic,
15
+ codeExecutionTool: () => codeExecutionTool,
14
16
  layer: () => layer,
15
17
  make: () => make,
16
- toCanonical: () => toCanonical
18
+ toCanonical: () => toCanonical,
19
+ webSearchTool: () => webSearchTool
17
20
  });
18
21
  /**
19
22
  * Provider-typed service tag. Yield this when you want Anthropic-specific
@@ -30,13 +33,28 @@ const outputConfig = (request) => pipe(Option.fromUndefinedOr(request.structured
30
33
  schema: format.schema["~standard"].jsonSchema.input({ target: "draft-2020-12" })
31
34
  } })));
32
35
  const resolvedMaxTokens = (cfg, request) => request.maxOutputTokens ?? cfg.defaultMaxTokens ?? FALLBACK_MAX_TOKENS;
33
- const toolDescriptors = (request) => {
34
- const descriptors = descriptorsOf(request.tools);
35
- return descriptors.length > 0 ? Option.some(descriptors.map((t) => ({
36
- name: t.name,
37
- description: t.description,
38
- input_schema: t.inputSchema
39
- }))) : Option.none();
36
+ /**
37
+ * One request-level breakpoint when caching is on. Anthropic places it on the
38
+ * last cacheable block itself, so nothing has to be marked per-block and the
39
+ * breakpoint stays correct as history grows.
40
+ */
41
+ const cacheControlOf = (cfg) => pipe(Option.fromUndefinedOr(cfg.promptCaching), Option.flatMap((opt) => opt === false ? Option.none() : Option.some({
42
+ type: "ephemeral",
43
+ ...opt !== true && opt.ttl !== void 0 && { ttl: opt.ttl }
44
+ })));
45
+ const functionToolDescriptors = (request) => descriptorsOf(request.tools).map((t) => ({
46
+ name: t.name,
47
+ description: t.description,
48
+ input_schema: t.inputSchema
49
+ }));
50
+ /**
51
+ * Merge the custom function-tool declarations with the native hosted-tool
52
+ * entries into the single `tools` array Anthropic expects. `providerWire` is
53
+ * pre-rendered (and validated) by the caller.
54
+ */
55
+ const toolsWire = (request, providerWire) => {
56
+ const tools = [...functionToolDescriptors(request), ...providerWire];
57
+ return tools.length > 0 ? Option.some(tools) : Option.none();
40
58
  };
41
59
  const toolChoiceWire = (request) => pipe(Option.fromUndefinedOr(request.toolChoice), Option.map((choice) => choice === "auto" ? { type: "auto" } : choice === "required" ? { type: "any" } : choice === "none" ? { type: "none" } : {
42
60
  type: "tool",
@@ -136,6 +154,14 @@ const buildNativeStream = (cfg) => {
136
154
  const url = `${cfg.baseUrl ?? "https://api.anthropic.com"}/v1/messages`;
137
155
  return (request) => Stream.unwrap(Effect.gen(function* () {
138
156
  const structured = outputConfig(request);
157
+ const providerWire = yield* Result.match(renderProviderTools(providerToolsOf(request.tools)), {
158
+ onFailure: (e) => Effect.fail(new AiError.Unsupported({
159
+ provider: "anthropic",
160
+ capability: e.capability,
161
+ reason: e.reason
162
+ })),
163
+ onSuccess: (wire) => Effect.succeed(wire)
164
+ });
139
165
  const bodyResult = buildRequestBody({
140
166
  model: request.model,
141
167
  history: request.history,
@@ -145,10 +171,11 @@ const buildNativeStream = (cfg) => {
145
171
  topK: Option.fromUndefinedOr(request.topK),
146
172
  stopSequences: Option.fromUndefinedOr(request.stopSequences),
147
173
  thinking: Option.fromUndefinedOr(request.thinking),
148
- tools: toolDescriptors(request),
174
+ tools: toolsWire(request, providerWire),
149
175
  toolChoice: toolChoiceWire(request),
150
176
  userId: Option.fromUndefinedOr(request.user),
151
- outputConfig: structured
177
+ outputConfig: structured,
178
+ cacheControl: cacheControlOf(cfg)
152
179
  });
153
180
  const body = yield* Result.match(bodyResult, {
154
181
  onFailure: (cause) => Effect.fail(new AiError.InvalidRequest({
@@ -190,7 +217,7 @@ const toCanonical = (s) => s.pipe(Stream.mapAccum(() => emptyAccumulator, (acc,
190
217
  /**
191
218
  * Build an `AnthropicService` value. For Layer-based setup, prefer `layer`.
192
219
  */
193
- const make = (cfg) => Effect.map(HttpClient.HttpClient.asEffect(), (client) => {
220
+ const make = (cfg) => Effect.map(HttpClient.HttpClient, (client) => {
194
221
  const streamNative = (request) => buildNativeStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client));
195
222
  const streamTurn = (request) => toCanonical(streamNative(request));
196
223
  return {
@@ -216,6 +243,6 @@ const layer = (cfg) => {
216
243
  return Layer.merge(typed, generic);
217
244
  };
218
245
  //#endregion
219
- export { Anthropic, layer, make, Anthropic_exports as t, toCanonical };
246
+ export { Anthropic, codeExecutionTool, layer, make, Anthropic_exports as t, toCanonical, webSearchTool };
220
247
 
221
248
  //# sourceMappingURL=Anthropic.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Anthropic.mjs","names":[],"sources":["../src/Anthropic.ts"],"sourcesContent":["import {\n Context,\n Effect,\n Layer,\n Match,\n Option,\n Redacted,\n Result,\n Schema,\n Stream,\n pipe,\n} from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport {\n type CommonRequest,\n LanguageModel,\n type LanguageModelService,\n turnFromStream,\n} from \"@effect-uai/core/LanguageModel\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport { descriptorsOf } from \"@effect-uai/core/Tool\"\nimport { type Turn, TurnEvent } from \"@effect-uai/core/Turn\"\nimport {\n type Accumulator,\n type ThinkingConfig,\n accumulatorToTurn,\n buildRequestBody,\n emptyAccumulator,\n} from \"./codec.js\"\nimport type { AnthropicModel } from \"./models.js\"\nimport { KnownProviderEvent, ProviderEvent, applyEvent } from \"./streamEvents.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type AnthropicRequest = Omit<CommonRequest, \"model\"> & {\n /**\n * Narrows `CommonRequest.model` (`string`) to the typed `AnthropicModel`\n * literal union for autocomplete.\n */\n readonly model: AnthropicModel\n /**\n * Top-K nucleus sampling parameter. Anthropic-specific; not exposed on the\n * common surface.\n */\n readonly topK?: number\n /** Stop sequences that abort generation when matched. */\n readonly stopSequences?: ReadonlyArray<string>\n /**\n * Extended thinking configuration. `0` budget is equivalent to disabled.\n * Only the `claude-sonnet-4-x`, `claude-haiku-4-x`, and pre-Opus-4.7\n * model lines support extended thinking.\n */\n readonly thinking?: ThinkingConfig\n /**\n * `metadata.user_id` on the wire. End-user tracking identifier.\n */\n readonly user?: string\n}\n\nexport type AnthropicService = {\n /**\n * Stream the provider's native event vocabulary (post-SSE-decode).\n * Use this when you need full vendor fidelity (e.g. `signature_delta` for\n * encrypted reasoning state). For provider-portable code, use `streamTurn`.\n */\n readonly streamNative: (\n request: AnthropicRequest,\n ) => Stream.Stream<ProviderEvent, AiError.AiError>\n /**\n * Stream canonical `TurnEvent`s. Implemented as\n * `streamNative |> toCanonical`.\n */\n readonly streamTurn: (request: AnthropicRequest) => Stream.Stream<TurnEvent, AiError.AiError>\n /**\n * Drain a single turn and return the assembled `Turn`. Derived from\n * `streamTurn` — Anthropic's non-streaming `/messages` endpoint\n * differs from the streamed one in subtle ways (no incremental\n * thinking deltas), so we route everything through streaming for\n * consistency.\n */\n readonly turn: (request: AnthropicRequest) => Effect.Effect<Turn, AiError.AiError>\n /**\n * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.\n * Stateful (threads an `Accumulator` for tool-call lookup and\n * accumulator-to-Turn assembly).\n */\n readonly toCanonical: <E, R>(\n s: Stream.Stream<ProviderEvent, E, R>,\n ) => Stream.Stream<TurnEvent, E, R>\n}\n\n/**\n * Provider-typed service tag. Yield this when you want Anthropic-specific\n * options (`topK`, `stopSequences`, `thinking`); yield the generic\n * `LanguageModel` tag for provider-portable code. Both are registered by\n * `layer`.\n */\nexport class Anthropic extends Context.Service<Anthropic, AnthropicService>()(\n \"@betalyra/effect-uai/providers/anthropic/Anthropic\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n /**\n * Default `max_tokens` for requests that don't override via\n * `request.maxOutputTokens`. Anthropic requires this field; we default to\n * 4096 if neither is set.\n */\n readonly defaultMaxTokens?: number\n}\n\nconst ANTHROPIC_VERSION = \"2023-06-01\"\nconst STRUCTURED_OUTPUTS_BETA = \"structured-outputs-2025-11-13\"\nconst FALLBACK_MAX_TOKENS = 4096\n\nconst outputConfig = (request: AnthropicRequest): Option.Option<Record<string, unknown>> =>\n pipe(\n Option.fromUndefinedOr(request.structured),\n Option.map((format) => ({\n format: {\n type: \"json_schema\",\n schema: format.schema[\"~standard\"].jsonSchema.input({ target: \"draft-2020-12\" }),\n },\n })),\n )\n\nconst resolvedMaxTokens = (cfg: Config, request: AnthropicRequest): number =>\n request.maxOutputTokens ?? cfg.defaultMaxTokens ?? FALLBACK_MAX_TOKENS\n\nconst toolDescriptors = (\n request: AnthropicRequest,\n): Option.Option<ReadonlyArray<Record<string, unknown>>> => {\n const descriptors = descriptorsOf(request.tools)\n return descriptors.length > 0\n ? Option.some(\n descriptors.map((t) => ({\n name: t.name,\n description: t.description,\n input_schema: t.inputSchema,\n })),\n )\n : Option.none()\n}\n\nconst toolChoiceWire = (request: AnthropicRequest): Option.Option<Record<string, unknown>> =>\n pipe(\n Option.fromUndefinedOr(request.toolChoice),\n Option.map((choice) =>\n choice === \"auto\"\n ? { type: \"auto\" }\n : choice === \"required\"\n ? { type: \"any\" }\n : choice === \"none\"\n ? { type: \"none\" }\n : { type: \"tool\", name: choice.name },\n ),\n )\n\n// ---------------------------------------------------------------------------\n// SSE event → ProviderEvent\n// ---------------------------------------------------------------------------\n\nconst decodeKnown = Schema.decodeUnknownEffect(KnownProviderEvent)\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\n\nconst makeUnknown = (raw: unknown): ProviderEvent => ({ type: \"_unknown\", raw })\n\n/**\n * Parse one SSE event's `data` payload into a typed `ProviderEvent`. Never\n * fails: JSON-parse and schema-decode failures both produce a synthesized\n * `_unknown` event so consumers of `streamNative` never silently miss a\n * wire event we didn't model. When the payload IS valid JSON but doesn't\n * match the known-event schema, `_unknown.raw` carries the parsed value;\n * when JSON parsing fails outright, it carries the raw string.\n */\nconst sseEventToProviderEvent = (ev: SSE.Event): Effect.Effect<ProviderEvent> =>\n parseJsonUnknown(ev.data).pipe(\n Effect.flatMap((parsed) =>\n decodeKnown(parsed).pipe(Effect.orElseSucceed(() => makeUnknown(parsed))),\n ),\n Effect.orElseSucceed(() => makeUnknown(ev.data)),\n )\n\n// ---------------------------------------------------------------------------\n// Per-event derivation of TurnEvents. Drives off the new accumulator and the\n// raw event, since some deltas (`tool_call_args_delta`) need the call_id\n// which lives on the accumulator's per-index block.\n// ---------------------------------------------------------------------------\n\nconst deltasFromEvent = (next: Accumulator, event: ProviderEvent): ReadonlyArray<TurnEvent> =>\n Match.value(event).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n content_block_start: (e) =>\n e.content_block.type === \"tool_use\"\n ? [\n TurnEvent.ToolCallStart({\n call_id: e.content_block.id,\n name: e.content_block.name,\n }),\n ]\n : [],\n content_block_delta: (e) =>\n Match.value(e.delta).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n text_delta: (d) => [TurnEvent.TextDelta({ text: d.text })],\n thinking_delta: (d) => [TurnEvent.ReasoningDelta({ text: d.thinking, kind: \"trace\" })],\n input_json_delta: (d) => {\n const block = next.blocks[e.index]\n if (block === undefined) return []\n const callId = Option.getOrElse(block.id, () => \"\")\n return callId.length === 0\n ? []\n : [TurnEvent.ToolCallArgsDelta({ call_id: callId, delta: d.partial_json })]\n },\n // Encrypted reasoning state - flows through `streamNative` but has\n // no canonical representation.\n signature_delta: () => [],\n }),\n ),\n message_start: (e) =>\n e.message.usage === undefined ? [] : [TurnEvent.UsageUpdate({ usage: next.usage })],\n message_delta: (e) =>\n e.usage === undefined ? [] : [TurnEvent.UsageUpdate({ usage: next.usage })],\n message_stop: () => [TurnEvent.TurnComplete({ turn: accumulatorToTurn(next) })],\n content_block_stop: () => [],\n ping: () => [],\n error: () => [],\n _unknown: () => [],\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"anthropic\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status === 529) return new AiError.Unavailable({ provider, status, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst buildNativeStream = (cfg: Config) => {\n const baseUrl = cfg.baseUrl ?? \"https://api.anthropic.com\"\n const url = `${baseUrl}/v1/messages`\n return (\n request: AnthropicRequest,\n ): Stream.Stream<ProviderEvent, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const structured = outputConfig(request)\n const bodyResult = buildRequestBody({\n model: request.model,\n history: request.history,\n maxTokens: resolvedMaxTokens(cfg, request),\n temperature: Option.fromUndefinedOr(request.temperature),\n topP: Option.fromUndefinedOr(request.topP),\n topK: Option.fromUndefinedOr(request.topK),\n stopSequences: Option.fromUndefinedOr(request.stopSequences),\n thinking: Option.fromUndefinedOr(request.thinking),\n tools: toolDescriptors(request),\n toolChoice: toolChoiceWire(request),\n userId: Option.fromUndefinedOr(request.user),\n outputConfig: structured,\n })\n\n const body = yield* Result.match(bodyResult, {\n onFailure: (cause) =>\n Effect.fail(\n new AiError.InvalidRequest({\n provider: \"anthropic\",\n param: \"input.function_call.arguments\",\n raw: cause,\n }),\n ),\n onSuccess: (b) => Effect.succeed(b),\n })\n\n const client = yield* HttpClient.HttpClient\n const baseRequest = HttpClientRequest.post(url).pipe(\n HttpClientRequest.setHeader(\"x-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.setHeader(\"anthropic-version\", ANTHROPIC_VERSION),\n HttpClientRequest.bodyJsonUnsafe(body),\n HttpClientRequest.accept(\"text/event-stream\"),\n )\n const httpRequest = Option.isSome(structured)\n ? baseRequest.pipe(HttpClientRequest.setHeader(\"anthropic-beta\", STRUCTURED_OUTPUTS_BETA))\n : baseRequest\n const response = yield* client\n .execute(httpRequest)\n .pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: \"anthropic\", raw: cause }),\n ),\n )\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return Stream.fail(httpStatusError(response.status, text))\n }\n\n return response.stream.pipe(\n Stream.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: \"anthropic\", raw: cause }),\n ),\n SSE.fromBytes,\n Stream.mapEffect(sseEventToProviderEvent),\n Stream.flatMap((event) =>\n event.type === \"error\"\n ? Stream.fail(new AiError.Unavailable({ provider: \"anthropic\", raw: event }))\n : Stream.succeed(event),\n ),\n )\n }),\n )\n}\n\n/**\n * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.\n * Threads a fresh `Accumulator` per stream so tool-call lookup and\n * `accumulatorToTurn` assembly work correctly across the run.\n */\nexport const toCanonical = <E, R>(\n s: Stream.Stream<ProviderEvent, E, R>,\n): Stream.Stream<TurnEvent, E, R> =>\n s.pipe(\n Stream.mapAccum(\n () => emptyAccumulator,\n (acc, event) => {\n const next = applyEvent(acc, event)\n const deltas = deltasFromEvent(next, event)\n return [next, deltas] as const\n },\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build an `AnthropicService` value. For Layer-based setup, prefer `layer`.\n */\nexport const make = (cfg: Config): Effect.Effect<AnthropicService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient.asEffect(), (client) => {\n const streamNative: AnthropicService[\"streamNative\"] = (request) =>\n buildNativeStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))\n const streamTurn: AnthropicService[\"streamTurn\"] = (request) =>\n toCanonical(streamNative(request))\n return {\n streamNative,\n streamTurn,\n turn: turnFromStream(streamTurn),\n toCanonical,\n }\n })\n\n/**\n * Layer that registers both the provider-specific `Anthropic` tag and the\n * generic `LanguageModel` tag, sharing one underlying implementation.\n *\n * The generic tag accepts `CommonRequest`; the typed tag accepts the full\n * `AnthropicRequest` surface.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<Anthropic | LanguageModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(Anthropic, make(cfg))\n const generic = Layer.effect(\n LanguageModel,\n Effect.map(\n make(cfg),\n (s): LanguageModelService => ({\n streamTurn: (request) => s.streamTurn(request as AnthropicRequest),\n turn: (request) => s.turn(request as AnthropicRequest),\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Anthropic.mjs","names":[],"sources":["../src/Anthropic.ts"],"sourcesContent":["import {\n Context,\n Effect,\n Layer,\n Match,\n Option,\n Redacted,\n Result,\n Schema,\n Stream,\n pipe,\n} from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport {\n type CommonRequest,\n LanguageModel,\n type LanguageModelService,\n turnFromStream,\n} from \"@effect-uai/core/LanguageModel\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport { descriptorsOf, providerToolsOf } from \"@effect-uai/core/Tool\"\nimport { type Turn, TurnEvent } from \"@effect-uai/core/Turn\"\nimport { renderProviderTools } from \"./AnthropicTools.js\"\nimport {\n type Accumulator,\n type CacheControl,\n type CacheTtl,\n type ThinkingConfig,\n accumulatorToTurn,\n buildRequestBody,\n emptyAccumulator,\n} from \"./codec.js\"\nimport type { AnthropicModel } from \"./models.js\"\nimport { KnownProviderEvent, ProviderEvent, applyEvent } from \"./streamEvents.js\"\n\nexport {\n codeExecutionTool,\n webSearchTool,\n type WebSearchOptions,\n type UserLocation,\n} from \"./AnthropicTools.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type AnthropicRequest = Omit<CommonRequest, \"model\"> & {\n /**\n * Narrows `CommonRequest.model` (`string`) to the typed `AnthropicModel`\n * literal union for autocomplete.\n */\n readonly model: AnthropicModel\n /**\n * Top-K nucleus sampling parameter. Anthropic-specific; not exposed on the\n * common surface.\n */\n readonly topK?: number\n /** Stop sequences that abort generation when matched. */\n readonly stopSequences?: ReadonlyArray<string>\n /**\n * Extended thinking configuration. `0` budget is equivalent to disabled.\n * Only the `claude-sonnet-4-x`, `claude-haiku-4-x`, and pre-Opus-4.7\n * model lines support extended thinking.\n */\n readonly thinking?: ThinkingConfig\n /**\n * `metadata.user_id` on the wire. End-user tracking identifier.\n */\n readonly user?: string\n}\n\nexport type AnthropicService = {\n /**\n * Stream the provider's native event vocabulary (post-SSE-decode).\n * Use this when you need full vendor fidelity (e.g. `signature_delta` for\n * encrypted reasoning state). For provider-portable code, use `streamTurn`.\n */\n readonly streamNative: (\n request: AnthropicRequest,\n ) => Stream.Stream<ProviderEvent, AiError.AiError>\n /**\n * Stream canonical `TurnEvent`s. Implemented as\n * `streamNative |> toCanonical`.\n */\n readonly streamTurn: (request: AnthropicRequest) => Stream.Stream<TurnEvent, AiError.AiError>\n /**\n * Drain a single turn and return the assembled `Turn`. Derived from\n * `streamTurn` — Anthropic's non-streaming `/messages` endpoint\n * differs from the streamed one in subtle ways (no incremental\n * thinking deltas), so we route everything through streaming for\n * consistency.\n */\n readonly turn: (request: AnthropicRequest) => Effect.Effect<Turn, AiError.AiError>\n /**\n * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.\n * Stateful (threads an `Accumulator` for tool-call lookup and\n * accumulator-to-Turn assembly).\n */\n readonly toCanonical: <E, R>(\n s: Stream.Stream<ProviderEvent, E, R>,\n ) => Stream.Stream<TurnEvent, E, R>\n}\n\n/**\n * Provider-typed service tag. Yield this when you want Anthropic-specific\n * options (`topK`, `stopSequences`, `thinking`); yield the generic\n * `LanguageModel` tag for provider-portable code. Both are registered by\n * `layer`.\n */\nexport class Anthropic extends Context.Service<Anthropic, AnthropicService>()(\n \"@betalyra/effect-uai/providers/anthropic/Anthropic\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n /**\n * Default `max_tokens` for requests that don't override via\n * `request.maxOutputTokens`. Anthropic requires this field; we default to\n * 4096 if neither is set.\n */\n readonly defaultMaxTokens?: number\n /**\n * Prompt caching. Off by default, so the library stays unopinionated about\n * a setting that changes billing. `true` is `{}`: cache with the 5-minute\n * default TTL.\n *\n * Anthropic caches the `tools` → `system` → `messages` prefix and moves the\n * breakpoint forward as the conversation grows, so a long system prompt and\n * tool definitions are paid for once rather than on every turn. Read\n * `usage.input_tokens_details.cached_tokens` to see it working.\n *\n * Mutating earlier history (rewriting an old message, compaction) breaks\n * the prefix from that point on. Anthropic falls back to the longest\n * unchanged prefix, so this costs savings but never correctness.\n */\n readonly promptCaching?: boolean | { readonly ttl?: CacheTtl }\n}\n\nconst ANTHROPIC_VERSION = \"2023-06-01\"\nconst STRUCTURED_OUTPUTS_BETA = \"structured-outputs-2025-11-13\"\nconst FALLBACK_MAX_TOKENS = 4096\n\nconst outputConfig = (request: AnthropicRequest): Option.Option<Record<string, unknown>> =>\n pipe(\n Option.fromUndefinedOr(request.structured),\n Option.map((format) => ({\n format: {\n type: \"json_schema\",\n schema: format.schema[\"~standard\"].jsonSchema.input({ target: \"draft-2020-12\" }),\n },\n })),\n )\n\nconst resolvedMaxTokens = (cfg: Config, request: AnthropicRequest): number =>\n request.maxOutputTokens ?? cfg.defaultMaxTokens ?? FALLBACK_MAX_TOKENS\n\n/**\n * One request-level breakpoint when caching is on. Anthropic places it on the\n * last cacheable block itself, so nothing has to be marked per-block and the\n * breakpoint stays correct as history grows.\n */\nconst cacheControlOf = (cfg: Config): Option.Option<CacheControl> =>\n pipe(\n Option.fromUndefinedOr(cfg.promptCaching),\n Option.flatMap((opt) =>\n opt === false\n ? Option.none<CacheControl>()\n : Option.some<CacheControl>({\n type: \"ephemeral\",\n ...(opt !== true && opt.ttl !== undefined && { ttl: opt.ttl }),\n }),\n ),\n )\n\nconst functionToolDescriptors = (\n request: AnthropicRequest,\n): ReadonlyArray<Record<string, unknown>> =>\n descriptorsOf(request.tools).map((t) => ({\n name: t.name,\n description: t.description,\n input_schema: t.inputSchema,\n }))\n\n/**\n * Merge the custom function-tool declarations with the native hosted-tool\n * entries into the single `tools` array Anthropic expects. `providerWire` is\n * pre-rendered (and validated) by the caller.\n */\nconst toolsWire = (\n request: AnthropicRequest,\n providerWire: ReadonlyArray<Record<string, unknown>>,\n): Option.Option<ReadonlyArray<Record<string, unknown>>> => {\n const tools = [...functionToolDescriptors(request), ...providerWire]\n return tools.length > 0 ? Option.some(tools) : Option.none()\n}\n\nconst toolChoiceWire = (request: AnthropicRequest): Option.Option<Record<string, unknown>> =>\n pipe(\n Option.fromUndefinedOr(request.toolChoice),\n Option.map((choice) =>\n choice === \"auto\"\n ? { type: \"auto\" }\n : choice === \"required\"\n ? { type: \"any\" }\n : choice === \"none\"\n ? { type: \"none\" }\n : { type: \"tool\", name: choice.name },\n ),\n )\n\n// ---------------------------------------------------------------------------\n// SSE event → ProviderEvent\n// ---------------------------------------------------------------------------\n\nconst decodeKnown = Schema.decodeUnknownEffect(KnownProviderEvent)\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\n\nconst makeUnknown = (raw: unknown): ProviderEvent => ({ type: \"_unknown\", raw })\n\n/**\n * Parse one SSE event's `data` payload into a typed `ProviderEvent`. Never\n * fails: JSON-parse and schema-decode failures both produce a synthesized\n * `_unknown` event so consumers of `streamNative` never silently miss a\n * wire event we didn't model. When the payload IS valid JSON but doesn't\n * match the known-event schema, `_unknown.raw` carries the parsed value;\n * when JSON parsing fails outright, it carries the raw string.\n */\nconst sseEventToProviderEvent = (ev: SSE.Event): Effect.Effect<ProviderEvent> =>\n parseJsonUnknown(ev.data).pipe(\n Effect.flatMap((parsed) =>\n decodeKnown(parsed).pipe(Effect.orElseSucceed(() => makeUnknown(parsed))),\n ),\n Effect.orElseSucceed(() => makeUnknown(ev.data)),\n )\n\n// ---------------------------------------------------------------------------\n// Per-event derivation of TurnEvents. Drives off the new accumulator and the\n// raw event, since some deltas (`tool_call_args_delta`) need the call_id\n// which lives on the accumulator's per-index block.\n// ---------------------------------------------------------------------------\n\nconst deltasFromEvent = (next: Accumulator, event: ProviderEvent): ReadonlyArray<TurnEvent> =>\n Match.value(event).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n content_block_start: (e) =>\n e.content_block.type === \"tool_use\"\n ? [\n TurnEvent.ToolCallStart({\n call_id: e.content_block.id,\n name: e.content_block.name,\n }),\n ]\n : [],\n content_block_delta: (e) =>\n Match.value(e.delta).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n text_delta: (d) => [TurnEvent.TextDelta({ text: d.text })],\n thinking_delta: (d) => [TurnEvent.ReasoningDelta({ text: d.thinking, kind: \"trace\" })],\n input_json_delta: (d) => {\n const block = next.blocks[e.index]\n if (block === undefined) return []\n const callId = Option.getOrElse(block.id, () => \"\")\n return callId.length === 0\n ? []\n : [TurnEvent.ToolCallArgsDelta({ call_id: callId, delta: d.partial_json })]\n },\n // Encrypted reasoning state - flows through `streamNative` but has\n // no canonical representation.\n signature_delta: () => [],\n }),\n ),\n message_start: (e) =>\n e.message.usage === undefined ? [] : [TurnEvent.UsageUpdate({ usage: next.usage })],\n message_delta: (e) =>\n e.usage === undefined ? [] : [TurnEvent.UsageUpdate({ usage: next.usage })],\n message_stop: () => [TurnEvent.TurnComplete({ turn: accumulatorToTurn(next) })],\n content_block_stop: () => [],\n ping: () => [],\n error: () => [],\n _unknown: () => [],\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"anthropic\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status === 529) return new AiError.Unavailable({ provider, status, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst buildNativeStream = (cfg: Config) => {\n const baseUrl = cfg.baseUrl ?? \"https://api.anthropic.com\"\n const url = `${baseUrl}/v1/messages`\n return (\n request: AnthropicRequest,\n ): Stream.Stream<ProviderEvent, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const structured = outputConfig(request)\n const providerWire = yield* Result.match(\n renderProviderTools(providerToolsOf(request.tools)),\n {\n onFailure: (e) =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"anthropic\",\n capability: e.capability,\n reason: e.reason,\n }),\n ),\n onSuccess: (wire) => Effect.succeed(wire),\n },\n )\n const bodyResult = buildRequestBody({\n model: request.model,\n history: request.history,\n maxTokens: resolvedMaxTokens(cfg, request),\n temperature: Option.fromUndefinedOr(request.temperature),\n topP: Option.fromUndefinedOr(request.topP),\n topK: Option.fromUndefinedOr(request.topK),\n stopSequences: Option.fromUndefinedOr(request.stopSequences),\n thinking: Option.fromUndefinedOr(request.thinking),\n tools: toolsWire(request, providerWire),\n toolChoice: toolChoiceWire(request),\n userId: Option.fromUndefinedOr(request.user),\n outputConfig: structured,\n cacheControl: cacheControlOf(cfg),\n })\n\n const body = yield* Result.match(bodyResult, {\n onFailure: (cause) =>\n Effect.fail(\n new AiError.InvalidRequest({\n provider: \"anthropic\",\n param: \"input.function_call.arguments\",\n raw: cause,\n }),\n ),\n onSuccess: (b) => Effect.succeed(b),\n })\n\n const client = yield* HttpClient.HttpClient\n const baseRequest = HttpClientRequest.post(url).pipe(\n HttpClientRequest.setHeader(\"x-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.setHeader(\"anthropic-version\", ANTHROPIC_VERSION),\n HttpClientRequest.bodyJsonUnsafe(body),\n HttpClientRequest.accept(\"text/event-stream\"),\n )\n const httpRequest = Option.isSome(structured)\n ? baseRequest.pipe(HttpClientRequest.setHeader(\"anthropic-beta\", STRUCTURED_OUTPUTS_BETA))\n : baseRequest\n const response = yield* client\n .execute(httpRequest)\n .pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: \"anthropic\", raw: cause }),\n ),\n )\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return Stream.fail(httpStatusError(response.status, text))\n }\n\n return response.stream.pipe(\n Stream.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: \"anthropic\", raw: cause }),\n ),\n SSE.fromBytes,\n Stream.mapEffect(sseEventToProviderEvent),\n Stream.flatMap((event) =>\n event.type === \"error\"\n ? Stream.fail(new AiError.Unavailable({ provider: \"anthropic\", raw: event }))\n : Stream.succeed(event),\n ),\n )\n }),\n )\n}\n\n/**\n * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.\n * Threads a fresh `Accumulator` per stream so tool-call lookup and\n * `accumulatorToTurn` assembly work correctly across the run.\n */\nexport const toCanonical = <E, R>(\n s: Stream.Stream<ProviderEvent, E, R>,\n): Stream.Stream<TurnEvent, E, R> =>\n s.pipe(\n Stream.mapAccum(\n () => emptyAccumulator,\n (acc, event) => {\n const next = applyEvent(acc, event)\n const deltas = deltasFromEvent(next, event)\n return [next, deltas] as const\n },\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build an `AnthropicService` value. For Layer-based setup, prefer `layer`.\n */\nexport const make = (cfg: Config): Effect.Effect<AnthropicService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => {\n const streamNative: AnthropicService[\"streamNative\"] = (request) =>\n buildNativeStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))\n const streamTurn: AnthropicService[\"streamTurn\"] = (request) =>\n toCanonical(streamNative(request))\n return {\n streamNative,\n streamTurn,\n turn: turnFromStream(streamTurn),\n toCanonical,\n }\n })\n\n/**\n * Layer that registers both the provider-specific `Anthropic` tag and the\n * generic `LanguageModel` tag, sharing one underlying implementation.\n *\n * The generic tag accepts `CommonRequest`; the typed tag accepts the full\n * `AnthropicRequest` surface.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<Anthropic | LanguageModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(Anthropic, make(cfg))\n const generic = Layer.effect(\n LanguageModel,\n Effect.map(\n make(cfg),\n (s): LanguageModelService => ({\n streamTurn: (request) => s.streamTurn(request as AnthropicRequest),\n turn: (request) => s.turn(request as AnthropicRequest),\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ import { Result, Schema } from "effect";
2
+ import * as Tool from "@effect-uai/core/Tool";
3
+
4
+ //#region src/AnthropicTools.d.ts
5
+ declare namespace AnthropicTools_d_exports {
6
+ export { AnthropicToolConfig, ToolRenderError, UserLocation, WebSearchOptions, codeExecutionTool, renderProviderTools, webSearchTool };
7
+ }
8
+ declare const UserLocation: Schema.Struct<{
9
+ readonly city: Schema.optional<Schema.String>;
10
+ readonly region: Schema.optional<Schema.String>;
11
+ readonly country: Schema.optional<Schema.String>;
12
+ readonly timezone: Schema.optional<Schema.String>;
13
+ }>;
14
+ type UserLocation = typeof UserLocation.Type;
15
+ /** Discriminated config for an Anthropic hosted tool. */
16
+ declare const AnthropicToolConfig: Schema.Union<readonly [Schema.Struct<{
17
+ readonly kind: Schema.Literal<"web_search">;
18
+ readonly type: Schema.String;
19
+ readonly maxUses: Schema.optional<Schema.Number>;
20
+ readonly allowedDomains: Schema.optional<Schema.$Array<Schema.String>>;
21
+ readonly blockedDomains: Schema.optional<Schema.$Array<Schema.String>>;
22
+ readonly userLocation: Schema.optional<Schema.Struct<{
23
+ readonly city: Schema.optional<Schema.String>;
24
+ readonly region: Schema.optional<Schema.String>;
25
+ readonly country: Schema.optional<Schema.String>;
26
+ readonly timezone: Schema.optional<Schema.String>;
27
+ }>>;
28
+ }>, Schema.Struct<{
29
+ readonly kind: Schema.Literal<"code_execution">;
30
+ readonly type: Schema.String;
31
+ }>]>;
32
+ type AnthropicToolConfig = typeof AnthropicToolConfig.Type;
33
+ type WebSearchOptions = {
34
+ readonly maxUses?: number;
35
+ readonly allowedDomains?: ReadonlyArray<string>;
36
+ readonly blockedDomains?: ReadonlyArray<string>;
37
+ readonly userLocation?: UserLocation;
38
+ };
39
+ /** Anthropic-hosted web search. */
40
+ declare const webSearchTool: (opts?: WebSearchOptions) => Tool.ProviderTool<"web_search", Record<string, never>, "anthropic", {
41
+ readonly maxUses?: number;
42
+ readonly allowedDomains?: ReadonlyArray<string>;
43
+ readonly blockedDomains?: ReadonlyArray<string>;
44
+ readonly userLocation?: UserLocation;
45
+ readonly kind: "web_search";
46
+ readonly type: "web_search_20250305";
47
+ }>;
48
+ /** Anthropic-hosted code execution (Python sandbox). */
49
+ declare const codeExecutionTool: Tool.ProviderTool<"code_execution", Record<string, never>, "anthropic", {
50
+ readonly kind: "code_execution";
51
+ readonly type: "code_execution_20250522";
52
+ }>;
53
+ /**
54
+ * A provider tool this adapter cannot render — a foreign `provider` or an
55
+ * unrecognized `config`. Carried as data so `Anthropic.ts` can lift it into a
56
+ * typed `AiError.Unsupported`.
57
+ */
58
+ type ToolRenderError = {
59
+ readonly capability: string;
60
+ readonly reason: string;
61
+ };
62
+ /**
63
+ * Render a toolkit's Anthropic provider tools to native `tools` entries. Fails
64
+ * with a `ToolRenderError` on the first foreign or unrecognized tool rather
65
+ * than dropping it.
66
+ */
67
+ declare const renderProviderTools: (tools: ReadonlyArray<Tool.ProviderTool<string, any, string, any>>) => Result.Result<ReadonlyArray<Record<string, unknown>>, ToolRenderError>;
68
+ //#endregion
69
+ export { AnthropicToolConfig, ToolRenderError, UserLocation, WebSearchOptions, codeExecutionTool, renderProviderTools, AnthropicTools_d_exports as t, webSearchTool };
70
+ //# sourceMappingURL=AnthropicTools.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnthropicTools.d.mts","names":[],"sources":["../src/AnthropicTools.ts"],"mappings":";;;;;;;cAeM,YAAA,EAAY,MAAA,CAAA,MAAA;EAAA;;;;;KAMN,YAAA,UAAsB,YAAA,CAAa,IAAA;;cAiBlC,mBAAA,EAAmB,MAAA,CAAA,KAAA,WAAA,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;;;KACpB,mBAAA,UAA6B,mBAAA,CAAoB,IAAA;AAAA,KAEjD,gBAAA;EAAA,SACD,OAAA;EAAA,SACA,cAAA,GAAiB,aAAA;EAAA,SACjB,cAAA,GAAiB,aAAA;EAAA,SACjB,YAAA,GAAe,YAAA;AAAA;;cAIb,aAAA,GAAiB,IAAA,GAAM,gBAAA,KAAqB,IAAA,CAAA,YAAA,eAAA,MAAA;EAAA;4BAN7B,aAAA;EAAA,0BACA,aAAA;EAAA,wBACF,YAAA;EAAA;;;;cAcb,iBAAA,EAAiB,IAAA,CAAA,YAAA,mBAAA,MAAA;EAAA;;;;;;;;KAalB,eAAA;EAAA,SACD,UAAA;EAAA,SACA,MAAA;AAAA;;AApCX;;;;cAsFa,mBAAA,GACX,KAAA,EAAO,aAAA,CAAc,IAAA,CAAK,YAAA,gCACzB,MAAA,CAAO,MAAA,CAAO,aAAA,CAAc,MAAA,oBAA0B,eAAA"}
@@ -0,0 +1,94 @@
1
+ import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
2
+ import { Match, Result, Schema } from "effect";
3
+ import * as Tool from "@effect-uai/core/Tool";
4
+ //#region src/AnthropicTools.ts
5
+ var AnthropicTools_exports = /* @__PURE__ */ __exportAll({
6
+ AnthropicToolConfig: () => AnthropicToolConfig,
7
+ codeExecutionTool: () => codeExecutionTool,
8
+ renderProviderTools: () => renderProviderTools,
9
+ webSearchTool: () => webSearchTool
10
+ });
11
+ const WEB_SEARCH_TYPE = "web_search_20250305";
12
+ const CODE_EXECUTION_TYPE = "code_execution_20250522";
13
+ const UserLocation = Schema.Struct({
14
+ city: Schema.optional(Schema.String),
15
+ region: Schema.optional(Schema.String),
16
+ country: Schema.optional(Schema.String),
17
+ timezone: Schema.optional(Schema.String)
18
+ });
19
+ const WebSearchConfig = Schema.Struct({
20
+ kind: Schema.Literal("web_search"),
21
+ type: Schema.String,
22
+ maxUses: Schema.optional(Schema.Number),
23
+ allowedDomains: Schema.optional(Schema.Array(Schema.String)),
24
+ blockedDomains: Schema.optional(Schema.Array(Schema.String)),
25
+ userLocation: Schema.optional(UserLocation)
26
+ });
27
+ const CodeExecutionConfig = Schema.Struct({
28
+ kind: Schema.Literal("code_execution"),
29
+ type: Schema.String
30
+ });
31
+ /** Discriminated config for an Anthropic hosted tool. */
32
+ const AnthropicToolConfig = Schema.Union([WebSearchConfig, CodeExecutionConfig]);
33
+ /** Anthropic-hosted web search. */
34
+ const webSearchTool = (opts = {}) => Tool.provider({
35
+ name: "web_search",
36
+ description: "Search the web, executed by Anthropic.",
37
+ inputSchema: Tool.noInput,
38
+ provider: "anthropic",
39
+ config: {
40
+ kind: "web_search",
41
+ type: WEB_SEARCH_TYPE,
42
+ ...opts
43
+ }
44
+ });
45
+ /** Anthropic-hosted code execution (Python sandbox). */
46
+ const codeExecutionTool = Tool.provider({
47
+ name: "code_execution",
48
+ description: "Run Python in a sandbox, executed by Anthropic.",
49
+ inputSchema: Tool.noInput,
50
+ provider: "anthropic",
51
+ config: {
52
+ kind: "code_execution",
53
+ type: CODE_EXECUTION_TYPE
54
+ }
55
+ });
56
+ const userLocationWire = (loc) => ({
57
+ type: "approximate",
58
+ ...loc.city !== void 0 && { city: loc.city },
59
+ ...loc.region !== void 0 && { region: loc.region },
60
+ ...loc.country !== void 0 && { country: loc.country },
61
+ ...loc.timezone !== void 0 && { timezone: loc.timezone }
62
+ });
63
+ const configToWire = Match.type().pipe(Match.discriminatorsExhaustive("kind")({
64
+ web_search: (c) => ({
65
+ type: c.type,
66
+ name: "web_search",
67
+ ...c.maxUses !== void 0 && { max_uses: c.maxUses },
68
+ ...c.allowedDomains !== void 0 && { allowed_domains: c.allowedDomains },
69
+ ...c.blockedDomains !== void 0 && { blocked_domains: c.blockedDomains },
70
+ ...c.userLocation !== void 0 && { user_location: userLocationWire(c.userLocation) }
71
+ }),
72
+ code_execution: (c) => ({
73
+ type: c.type,
74
+ name: "code_execution"
75
+ })
76
+ }));
77
+ const decodeConfig = Schema.decodeUnknownResult(AnthropicToolConfig);
78
+ const renderOne = (tool) => tool.provider !== "anthropic" ? Result.fail({
79
+ capability: `providerTool:${tool.provider}`,
80
+ reason: `tool "${tool.name}" is hosted by "${tool.provider}", not anthropic`
81
+ }) : decodeConfig(tool.config).pipe(Result.map(configToWire), Result.mapError(() => ({
82
+ capability: "providerTool",
83
+ reason: `unrecognized Anthropic provider tool config on "${tool.name}"`
84
+ })));
85
+ /**
86
+ * Render a toolkit's Anthropic provider tools to native `tools` entries. Fails
87
+ * with a `ToolRenderError` on the first foreign or unrecognized tool rather
88
+ * than dropping it.
89
+ */
90
+ const renderProviderTools = (tools) => Result.all(tools.map(renderOne));
91
+ //#endregion
92
+ export { AnthropicToolConfig, codeExecutionTool, renderProviderTools, AnthropicTools_exports as t, webSearchTool };
93
+
94
+ //# sourceMappingURL=AnthropicTools.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnthropicTools.mjs","names":[],"sources":["../src/AnthropicTools.ts"],"sourcesContent":["import { Match, Result, Schema } from \"effect\"\nimport * as Tool from \"@effect-uai/core/Tool\"\n\n// ---------------------------------------------------------------------------\n// Anthropic provider-hosted tools. Each is a `Tool.provider` whose `config`\n// carries the version-dated wire `type` string plus its options; the renderer\n// maps it to a native entry appended to the `tools` array alongside custom\n// tools. The `inputSchema` is never sent (provider tools are partitioned out\n// of the custom-tool declarations), so `Tool.noInput` suffices.\n// Reference: https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/web-search-tool\n// ---------------------------------------------------------------------------\n\nconst WEB_SEARCH_TYPE = \"web_search_20250305\"\nconst CODE_EXECUTION_TYPE = \"code_execution_20250522\"\n\nconst UserLocation = Schema.Struct({\n city: Schema.optional(Schema.String),\n region: Schema.optional(Schema.String),\n country: Schema.optional(Schema.String),\n timezone: Schema.optional(Schema.String),\n})\nexport type UserLocation = typeof UserLocation.Type\n\nconst WebSearchConfig = Schema.Struct({\n kind: Schema.Literal(\"web_search\"),\n type: Schema.String,\n maxUses: Schema.optional(Schema.Number),\n allowedDomains: Schema.optional(Schema.Array(Schema.String)),\n blockedDomains: Schema.optional(Schema.Array(Schema.String)),\n userLocation: Schema.optional(UserLocation),\n})\n\nconst CodeExecutionConfig = Schema.Struct({\n kind: Schema.Literal(\"code_execution\"),\n type: Schema.String,\n})\n\n/** Discriminated config for an Anthropic hosted tool. */\nexport const AnthropicToolConfig = Schema.Union([WebSearchConfig, CodeExecutionConfig])\nexport type AnthropicToolConfig = typeof AnthropicToolConfig.Type\n\nexport type WebSearchOptions = {\n readonly maxUses?: number\n readonly allowedDomains?: ReadonlyArray<string>\n readonly blockedDomains?: ReadonlyArray<string>\n readonly userLocation?: UserLocation\n}\n\n/** Anthropic-hosted web search. */\nexport const webSearchTool = (opts: WebSearchOptions = {}) =>\n Tool.provider({\n name: \"web_search\",\n description: \"Search the web, executed by Anthropic.\",\n inputSchema: Tool.noInput,\n provider: \"anthropic\",\n config: { kind: \"web_search\", type: WEB_SEARCH_TYPE, ...opts } as const,\n })\n\n/** Anthropic-hosted code execution (Python sandbox). */\nexport const codeExecutionTool = Tool.provider({\n name: \"code_execution\",\n description: \"Run Python in a sandbox, executed by Anthropic.\",\n inputSchema: Tool.noInput,\n provider: \"anthropic\",\n config: { kind: \"code_execution\", type: CODE_EXECUTION_TYPE } as const,\n})\n\n/**\n * A provider tool this adapter cannot render — a foreign `provider` or an\n * unrecognized `config`. Carried as data so `Anthropic.ts` can lift it into a\n * typed `AiError.Unsupported`.\n */\nexport type ToolRenderError = {\n readonly capability: string\n readonly reason: string\n}\n\nconst userLocationWire = (loc: UserLocation): Record<string, unknown> => ({\n type: \"approximate\",\n ...(loc.city !== undefined && { city: loc.city }),\n ...(loc.region !== undefined && { region: loc.region }),\n ...(loc.country !== undefined && { country: loc.country }),\n ...(loc.timezone !== undefined && { timezone: loc.timezone }),\n})\n\nconst configToWire = Match.type<AnthropicToolConfig>().pipe(\n Match.discriminatorsExhaustive(\"kind\")({\n web_search: (c): Record<string, unknown> => ({\n type: c.type,\n name: \"web_search\",\n ...(c.maxUses !== undefined && { max_uses: c.maxUses }),\n ...(c.allowedDomains !== undefined && { allowed_domains: c.allowedDomains }),\n ...(c.blockedDomains !== undefined && { blocked_domains: c.blockedDomains }),\n ...(c.userLocation !== undefined && { user_location: userLocationWire(c.userLocation) }),\n }),\n code_execution: (c): Record<string, unknown> => ({ type: c.type, name: \"code_execution\" }),\n }),\n)\n\nconst decodeConfig = Schema.decodeUnknownResult(AnthropicToolConfig)\n\nconst renderOne = (\n tool: Tool.ProviderTool<string, any, string, any>,\n): Result.Result<Record<string, unknown>, ToolRenderError> =>\n tool.provider !== \"anthropic\"\n ? Result.fail({\n capability: `providerTool:${tool.provider}`,\n reason: `tool \"${tool.name}\" is hosted by \"${tool.provider}\", not anthropic`,\n })\n : decodeConfig(tool.config).pipe(\n Result.map(configToWire),\n Result.mapError(\n (): ToolRenderError => ({\n capability: \"providerTool\",\n reason: `unrecognized Anthropic provider tool config on \"${tool.name}\"`,\n }),\n ),\n )\n\n/**\n * Render a toolkit's Anthropic provider tools to native `tools` entries. Fails\n * with a `ToolRenderError` on the first foreign or unrecognized tool rather\n * than dropping it.\n */\nexport const renderProviderTools = (\n tools: ReadonlyArray<Tool.ProviderTool<string, any, string, any>>,\n): Result.Result<ReadonlyArray<Record<string, unknown>>, ToolRenderError> =>\n Result.all(tools.map(renderOne))\n"],"mappings":";;;;;;;;;;AAYA,MAAM,kBAAkB;AACxB,MAAM,sBAAsB;AAE5B,MAAM,eAAe,OAAO,OAAO;CACjC,MAAM,OAAO,SAAS,OAAO,OAAO;CACpC,QAAQ,OAAO,SAAS,OAAO,OAAO;CACtC,SAAS,OAAO,SAAS,OAAO,OAAO;CACvC,UAAU,OAAO,SAAS,OAAO,OAAO;CACzC,CAAC;AAGF,MAAM,kBAAkB,OAAO,OAAO;CACpC,MAAM,OAAO,QAAQ,aAAa;CAClC,MAAM,OAAO;CACb,SAAS,OAAO,SAAS,OAAO,OAAO;CACvC,gBAAgB,OAAO,SAAS,OAAO,MAAM,OAAO,OAAO,CAAC;CAC5D,gBAAgB,OAAO,SAAS,OAAO,MAAM,OAAO,OAAO,CAAC;CAC5D,cAAc,OAAO,SAAS,aAAa;CAC5C,CAAC;AAEF,MAAM,sBAAsB,OAAO,OAAO;CACxC,MAAM,OAAO,QAAQ,iBAAiB;CACtC,MAAM,OAAO;CACd,CAAC;;AAGF,MAAa,sBAAsB,OAAO,MAAM,CAAC,iBAAiB,oBAAoB,CAAC;;AAWvF,MAAa,iBAAiB,OAAyB,EAAE,KACvD,KAAK,SAAS;CACZ,MAAM;CACN,aAAa;CACb,aAAa,KAAK;CAClB,UAAU;CACV,QAAQ;EAAE,MAAM;EAAc,MAAM;EAAiB,GAAG;EAAM;CAC/D,CAAC;;AAGJ,MAAa,oBAAoB,KAAK,SAAS;CAC7C,MAAM;CACN,aAAa;CACb,aAAa,KAAK;CAClB,UAAU;CACV,QAAQ;EAAE,MAAM;EAAkB,MAAM;EAAqB;CAC9D,CAAC;AAYF,MAAM,oBAAoB,SAAgD;CACxE,MAAM;CACN,GAAI,IAAI,SAAS,KAAA,KAAa,EAAE,MAAM,IAAI,MAAM;CAChD,GAAI,IAAI,WAAW,KAAA,KAAa,EAAE,QAAQ,IAAI,QAAQ;CACtD,GAAI,IAAI,YAAY,KAAA,KAAa,EAAE,SAAS,IAAI,SAAS;CACzD,GAAI,IAAI,aAAa,KAAA,KAAa,EAAE,UAAU,IAAI,UAAU;CAC7D;AAED,MAAM,eAAe,MAAM,MAA2B,CAAC,KACrD,MAAM,yBAAyB,OAAO,CAAC;CACrC,aAAa,OAAgC;EAC3C,MAAM,EAAE;EACR,MAAM;EACN,GAAI,EAAE,YAAY,KAAA,KAAa,EAAE,UAAU,EAAE,SAAS;EACtD,GAAI,EAAE,mBAAmB,KAAA,KAAa,EAAE,iBAAiB,EAAE,gBAAgB;EAC3E,GAAI,EAAE,mBAAmB,KAAA,KAAa,EAAE,iBAAiB,EAAE,gBAAgB;EAC3E,GAAI,EAAE,iBAAiB,KAAA,KAAa,EAAE,eAAe,iBAAiB,EAAE,aAAa,EAAE;EACxF;CACD,iBAAiB,OAAgC;EAAE,MAAM,EAAE;EAAM,MAAM;EAAkB;CAC1F,CAAC,CACH;AAED,MAAM,eAAe,OAAO,oBAAoB,oBAAoB;AAEpE,MAAM,aACJ,SAEA,KAAK,aAAa,cACd,OAAO,KAAK;CACV,YAAY,gBAAgB,KAAK;CACjC,QAAQ,SAAS,KAAK,KAAK,kBAAkB,KAAK,SAAS;CAC5D,CAAC,GACF,aAAa,KAAK,OAAO,CAAC,KACxB,OAAO,IAAI,aAAa,EACxB,OAAO,gBACmB;CACtB,YAAY;CACZ,QAAQ,mDAAmD,KAAK,KAAK;CACtE,EACF,CACF;;;;;;AAOP,MAAa,uBACX,UAEA,OAAO,IAAI,MAAM,IAAI,UAAU,CAAC"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,156 @@
1
+ import { codeExecutionTool, renderProviderTools, webSearchTool } from "./AnthropicTools.mjs";
2
+ import { accumulatorToTurn, buildRequestBody, emptyAccumulator } from "./codec.mjs";
3
+ import { KnownProviderEvent, applyEvent } from "./streamEvents.mjs";
4
+ import { n as describe, r as it, t as globalExpect } from "./test.DNmyFkvJ-CPKukVWS.mjs";
5
+ import { Effect, Option, Result, Schema } from "effect";
6
+ import * as Tool from "@effect-uai/core/Tool";
7
+ import { descriptorsOf, providerToolsOf } from "@effect-uai/core/Tool";
8
+ //#region src/anthropicTools.test.ts
9
+ const localTool = Tool.make({
10
+ name: "get_weather",
11
+ description: "Look up the weather.",
12
+ inputSchema: Tool.fromEffectSchema(Schema.Struct({ city: Schema.String })),
13
+ run: ({ city }) => Effect.succeed(`sunny in ${city}`)
14
+ });
15
+ const wireFor = (toolkit) => Result.getOrThrow(renderProviderTools(providerToolsOf(toolkit)));
16
+ const functionWire = (toolkit) => descriptorsOf(toolkit).map((t) => ({
17
+ name: t.name,
18
+ description: t.description,
19
+ input_schema: t.inputSchema
20
+ }));
21
+ const buildWith = (toolkit, toolChoice = Option.none()) => {
22
+ const tools = [...functionWire(toolkit), ...wireFor(toolkit)];
23
+ return Result.getOrThrow(buildRequestBody({
24
+ model: "claude-sonnet-4-5",
25
+ history: [{
26
+ type: "message",
27
+ role: "user",
28
+ content: [{
29
+ type: "input_text",
30
+ text: "hi"
31
+ }]
32
+ }],
33
+ maxTokens: 1024,
34
+ temperature: Option.none(),
35
+ topP: Option.none(),
36
+ topK: Option.none(),
37
+ stopSequences: Option.none(),
38
+ thinking: Option.none(),
39
+ tools: tools.length > 0 ? Option.some(tools) : Option.none(),
40
+ toolChoice,
41
+ userId: Option.none(),
42
+ outputConfig: Option.none(),
43
+ cacheControl: Option.none()
44
+ }));
45
+ };
46
+ describe("Anthropic provider tools", () => {
47
+ it("renders web_search to the native wire entry with options", () => {
48
+ globalExpect(wireFor({ web_search: webSearchTool({
49
+ maxUses: 3,
50
+ allowedDomains: ["example.com"]
51
+ }) })).toEqual([{
52
+ type: "web_search_20250305",
53
+ name: "web_search",
54
+ max_uses: 3,
55
+ allowed_domains: ["example.com"]
56
+ }]);
57
+ });
58
+ it("renders code_execution to its native wire entry", () => {
59
+ globalExpect(wireFor({ code_execution: codeExecutionTool })).toEqual([{
60
+ type: "code_execution_20250522",
61
+ name: "code_execution"
62
+ }]);
63
+ });
64
+ it("emits both a custom tool and the native entry in the same tools array", () => {
65
+ globalExpect(buildWith({
66
+ get_weather: localTool,
67
+ web_search: webSearchTool()
68
+ }).tools).toEqual([globalExpect.objectContaining({ name: "get_weather" }), {
69
+ type: "web_search_20250305",
70
+ name: "web_search"
71
+ }]);
72
+ });
73
+ it("omits tool_choice for a native-only request", () => {
74
+ const body = buildWith({ web_search: webSearchTool() });
75
+ globalExpect(body.tools).toHaveLength(1);
76
+ globalExpect(body.tool_choice).toBeUndefined();
77
+ });
78
+ it("fails on a foreign provider tool", () => {
79
+ const foreign = Tool.provider({
80
+ name: "google_search",
81
+ description: "Gemini grounding.",
82
+ inputSchema: Tool.noInput,
83
+ provider: "gemini",
84
+ config: { kind: "google_search" }
85
+ });
86
+ globalExpect(Result.isFailure(renderProviderTools(providerToolsOf({ google_search: foreign })))).toBe(true);
87
+ });
88
+ it("fails on an unrecognized config", () => {
89
+ const bogus = Tool.provider({
90
+ name: "mystery",
91
+ description: "Unknown Anthropic tool.",
92
+ inputSchema: Tool.noInput,
93
+ provider: "anthropic",
94
+ config: { kind: "telepathy" }
95
+ });
96
+ globalExpect(Result.isFailure(renderProviderTools(providerToolsOf({ mystery: bogus })))).toBe(true);
97
+ });
98
+ });
99
+ describe("Anthropic hosted-tool response tolerance", () => {
100
+ it("does not decode a server_tool_use content block as a known event", () => {
101
+ const decoded = Schema.decodeUnknownResult(KnownProviderEvent)({
102
+ type: "content_block_start",
103
+ index: 0,
104
+ content_block: {
105
+ type: "server_tool_use",
106
+ id: "srvtoolu_1",
107
+ name: "web_search",
108
+ input: {}
109
+ }
110
+ });
111
+ globalExpect(Result.isFailure(decoded)).toBe(true);
112
+ });
113
+ it("builds the text turn when hosted-tool blocks arrive as _unknown", () => {
114
+ globalExpect(accumulatorToTurn([
115
+ {
116
+ type: "_unknown",
117
+ raw: {
118
+ type: "content_block_start",
119
+ content_block: "server_tool_use"
120
+ }
121
+ },
122
+ {
123
+ type: "content_block_start",
124
+ index: 1,
125
+ content_block: {
126
+ type: "text",
127
+ text: ""
128
+ }
129
+ },
130
+ {
131
+ type: "content_block_delta",
132
+ index: 1,
133
+ delta: {
134
+ type: "text_delta",
135
+ text: "Grounded answer."
136
+ }
137
+ },
138
+ {
139
+ type: "content_block_stop",
140
+ index: 1
141
+ },
142
+ { type: "message_stop" }
143
+ ].reduce(applyEvent, emptyAccumulator)).items).toEqual([{
144
+ type: "message",
145
+ role: "assistant",
146
+ content: [{
147
+ type: "output_text",
148
+ text: "Grounded answer."
149
+ }]
150
+ }]);
151
+ });
152
+ });
153
+ //#endregion
154
+ export {};
155
+
156
+ //# sourceMappingURL=anthropicTools.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropicTools.test.mjs","names":["expect"],"sources":["../src/anthropicTools.test.ts"],"sourcesContent":["import { Effect, Option, Result, Schema } from \"effect\"\nimport * as Tool from \"@effect-uai/core/Tool\"\nimport { descriptorsOf, providerToolsOf } from \"@effect-uai/core/Tool\"\nimport { describe, expect, it } from \"vitest\"\nimport { codeExecutionTool, renderProviderTools, webSearchTool } from \"./AnthropicTools.js\"\nimport { accumulatorToTurn, buildRequestBody, emptyAccumulator } from \"./codec.js\"\nimport { KnownProviderEvent, applyEvent } from \"./streamEvents.js\"\n\nconst localTool = Tool.make({\n name: \"get_weather\",\n description: \"Look up the weather.\",\n inputSchema: Tool.fromEffectSchema(Schema.Struct({ city: Schema.String })),\n run: ({ city }) => Effect.succeed(`sunny in ${city}`),\n})\n\nconst wireFor = (toolkit: Record<string, Tool.AnyTool>) =>\n Result.getOrThrow(renderProviderTools(providerToolsOf(toolkit)))\n\nconst functionWire = (toolkit: Record<string, Tool.AnyTool>) =>\n descriptorsOf(toolkit).map((t) => ({\n name: t.name,\n description: t.description,\n input_schema: t.inputSchema,\n }))\n\nconst buildWith = (\n toolkit: Record<string, Tool.AnyTool>,\n toolChoice: Option.Option<Record<string, unknown>> = Option.none(),\n) => {\n const tools = [...functionWire(toolkit), ...wireFor(toolkit)]\n return Result.getOrThrow(\n buildRequestBody({\n model: \"claude-sonnet-4-5\",\n history: [{ type: \"message\", role: \"user\", content: [{ type: \"input_text\", text: \"hi\" }] }],\n maxTokens: 1024,\n temperature: Option.none(),\n topP: Option.none(),\n topK: Option.none(),\n stopSequences: Option.none(),\n thinking: Option.none(),\n tools: tools.length > 0 ? Option.some(tools) : Option.none(),\n toolChoice,\n userId: Option.none(),\n outputConfig: Option.none(),\n cacheControl: Option.none(),\n }),\n )\n}\n\ndescribe(\"Anthropic provider tools\", () => {\n it(\"renders web_search to the native wire entry with options\", () => {\n const wire = wireFor({\n web_search: webSearchTool({ maxUses: 3, allowedDomains: [\"example.com\"] }),\n })\n expect(wire).toEqual([\n {\n type: \"web_search_20250305\",\n name: \"web_search\",\n max_uses: 3,\n allowed_domains: [\"example.com\"],\n },\n ])\n })\n\n it(\"renders code_execution to its native wire entry\", () => {\n expect(wireFor({ code_execution: codeExecutionTool })).toEqual([\n { type: \"code_execution_20250522\", name: \"code_execution\" },\n ])\n })\n\n it(\"emits both a custom tool and the native entry in the same tools array\", () => {\n const body = buildWith({ get_weather: localTool, web_search: webSearchTool() })\n expect(body.tools).toEqual([\n expect.objectContaining({ name: \"get_weather\" }),\n { type: \"web_search_20250305\", name: \"web_search\" },\n ])\n })\n\n it(\"omits tool_choice for a native-only request\", () => {\n const body = buildWith({ web_search: webSearchTool() })\n expect(body.tools).toHaveLength(1)\n expect(body.tool_choice).toBeUndefined()\n })\n\n it(\"fails on a foreign provider tool\", () => {\n const foreign = Tool.provider({\n name: \"google_search\",\n description: \"Gemini grounding.\",\n inputSchema: Tool.noInput,\n provider: \"gemini\",\n config: { kind: \"google_search\" },\n })\n expect(Result.isFailure(renderProviderTools(providerToolsOf({ google_search: foreign })))).toBe(\n true,\n )\n })\n\n it(\"fails on an unrecognized config\", () => {\n const bogus = Tool.provider({\n name: \"mystery\",\n description: \"Unknown Anthropic tool.\",\n inputSchema: Tool.noInput,\n provider: \"anthropic\",\n config: { kind: \"telepathy\" },\n })\n expect(Result.isFailure(renderProviderTools(providerToolsOf({ mystery: bogus })))).toBe(true)\n })\n})\n\ndescribe(\"Anthropic hosted-tool response tolerance\", () => {\n it(\"does not decode a server_tool_use content block as a known event\", () => {\n const serverToolUse = {\n type: \"content_block_start\",\n index: 0,\n content_block: { type: \"server_tool_use\", id: \"srvtoolu_1\", name: \"web_search\", input: {} },\n }\n const decoded = Schema.decodeUnknownResult(KnownProviderEvent)(serverToolUse)\n // Fails to decode → the SSE layer synthesizes `_unknown`, which is ignored.\n expect(Result.isFailure(decoded)).toBe(true)\n })\n\n it(\"builds the text turn when hosted-tool blocks arrive as _unknown\", () => {\n // Sequence: server_tool_use + web_search_tool_result blocks flow through as\n // `_unknown` (no accumulator change); the assistant's text answer is a\n // normal text block and survives into the turn.\n const events = [\n { type: \"_unknown\", raw: { type: \"content_block_start\", content_block: \"server_tool_use\" } },\n { type: \"content_block_start\", index: 1, content_block: { type: \"text\", text: \"\" } },\n {\n type: \"content_block_delta\",\n index: 1,\n delta: { type: \"text_delta\", text: \"Grounded answer.\" },\n },\n { type: \"content_block_stop\", index: 1 },\n { type: \"message_stop\" },\n ] as const\n const acc = events.reduce(applyEvent, emptyAccumulator)\n const turn = accumulatorToTurn(acc)\n expect(turn.items).toEqual([\n {\n type: \"message\",\n role: \"assistant\",\n content: [{ type: \"output_text\", text: \"Grounded answer.\" }],\n },\n ])\n })\n})\n"],"mappings":";;;;;;;;AAQA,MAAM,YAAY,KAAK,KAAK;CAC1B,MAAM;CACN,aAAa;CACb,aAAa,KAAK,iBAAiB,OAAO,OAAO,EAAE,MAAM,OAAO,QAAQ,CAAC,CAAC;CAC1E,MAAM,EAAE,WAAW,OAAO,QAAQ,YAAY,OAAO;CACtD,CAAC;AAEF,MAAM,WAAW,YACf,OAAO,WAAW,oBAAoB,gBAAgB,QAAQ,CAAC,CAAC;AAElE,MAAM,gBAAgB,YACpB,cAAc,QAAQ,CAAC,KAAK,OAAO;CACjC,MAAM,EAAE;CACR,aAAa,EAAE;CACf,cAAc,EAAE;CACjB,EAAE;AAEL,MAAM,aACJ,SACA,aAAqD,OAAO,MAAM,KAC/D;CACH,MAAM,QAAQ,CAAC,GAAG,aAAa,QAAQ,EAAE,GAAG,QAAQ,QAAQ,CAAC;AAC7D,QAAO,OAAO,WACZ,iBAAiB;EACf,OAAO;EACP,SAAS,CAAC;GAAE,MAAM;GAAW,MAAM;GAAQ,SAAS,CAAC;IAAE,MAAM;IAAc,MAAM;IAAM,CAAC;GAAE,CAAC;EAC3F,WAAW;EACX,aAAa,OAAO,MAAM;EAC1B,MAAM,OAAO,MAAM;EACnB,MAAM,OAAO,MAAM;EACnB,eAAe,OAAO,MAAM;EAC5B,UAAU,OAAO,MAAM;EACvB,OAAO,MAAM,SAAS,IAAI,OAAO,KAAK,MAAM,GAAG,OAAO,MAAM;EAC5D;EACA,QAAQ,OAAO,MAAM;EACrB,cAAc,OAAO,MAAM;EAC3B,cAAc,OAAO,MAAM;EAC5B,CAAC,CACH;;AAGH,SAAS,kCAAkC;AACzC,IAAG,kEAAkE;AAInE,eAHa,QAAQ,EACnB,YAAY,cAAc;GAAE,SAAS;GAAG,gBAAgB,CAAC,cAAc;GAAE,CAAC,EAC3E,CACU,CAAC,CAAC,QAAQ,CACnB;GACE,MAAM;GACN,MAAM;GACN,UAAU;GACV,iBAAiB,CAAC,cAAc;GACjC,CACF,CAAC;GACF;AAEF,IAAG,yDAAyD;AAC1D,eAAO,QAAQ,EAAE,gBAAgB,mBAAmB,CAAC,CAAC,CAAC,QAAQ,CAC7D;GAAE,MAAM;GAA2B,MAAM;GAAkB,CAC5D,CAAC;GACF;AAEF,IAAG,+EAA+E;AAEhF,eADa,UAAU;GAAE,aAAa;GAAW,YAAY,eAAe;GAAE,CACnE,CAAC,MAAM,CAAC,QAAQ,CACzBA,aAAO,iBAAiB,EAAE,MAAM,eAAe,CAAC,EAChD;GAAE,MAAM;GAAuB,MAAM;GAAc,CACpD,CAAC;GACF;AAEF,IAAG,qDAAqD;EACtD,MAAM,OAAO,UAAU,EAAE,YAAY,eAAe,EAAE,CAAC;AACvD,eAAO,KAAK,MAAM,CAAC,aAAa,EAAE;AAClC,eAAO,KAAK,YAAY,CAAC,eAAe;GACxC;AAEF,IAAG,0CAA0C;EAC3C,MAAM,UAAU,KAAK,SAAS;GAC5B,MAAM;GACN,aAAa;GACb,aAAa,KAAK;GAClB,UAAU;GACV,QAAQ,EAAE,MAAM,iBAAiB;GAClC,CAAC;AACF,eAAO,OAAO,UAAU,oBAAoB,gBAAgB,EAAE,eAAe,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,KACzF,KACD;GACD;AAEF,IAAG,yCAAyC;EAC1C,MAAM,QAAQ,KAAK,SAAS;GAC1B,MAAM;GACN,aAAa;GACb,aAAa,KAAK;GAClB,UAAU;GACV,QAAQ,EAAE,MAAM,aAAa;GAC9B,CAAC;AACF,eAAO,OAAO,UAAU,oBAAoB,gBAAgB,EAAE,SAAS,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK;GAC7F;EACF;AAEF,SAAS,kDAAkD;AACzD,IAAG,0EAA0E;EAM3E,MAAM,UAAU,OAAO,oBAAoB,mBAAmB,CAAC;GAJ7D,MAAM;GACN,OAAO;GACP,eAAe;IAAE,MAAM;IAAmB,IAAI;IAAc,MAAM;IAAc,OAAO,EAAE;IAAE;GAEjB,CAAC;AAE7E,eAAO,OAAO,UAAU,QAAQ,CAAC,CAAC,KAAK,KAAK;GAC5C;AAEF,IAAG,yEAAyE;AAiB1E,eADa,kBADD;GAVV;IAAE,MAAM;IAAY,KAAK;KAAE,MAAM;KAAuB,eAAe;KAAmB;IAAE;GAC5F;IAAE,MAAM;IAAuB,OAAO;IAAG,eAAe;KAAE,MAAM;KAAQ,MAAM;KAAI;IAAE;GACpF;IACE,MAAM;IACN,OAAO;IACP,OAAO;KAAE,MAAM;KAAc,MAAM;KAAoB;IACxD;GACD;IAAE,MAAM;IAAsB,OAAO;IAAG;GACxC,EAAE,MAAM,gBAAgB;GAER,CAAC,OAAO,YAAY,iBACJ,CACvB,CAAC,MAAM,CAAC,QAAQ,CACzB;GACE,MAAM;GACN,MAAM;GACN,SAAS,CAAC;IAAE,MAAM;IAAe,MAAM;IAAoB,CAAC;GAC7D,CACF,CAAC;GACF;EACF"}