Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/effect-program-design/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Build deep Fold modules: a small, domain-shaped public interface hides descripto
details, persistence, resource ownership, and workflow coordination. Keep expected failures in Effect's error channel,
dependencies in `R`, resources in scopes, and external values at explicit schema boundaries.

Fold is an Effect v4 (`4.0.0-rc.109`) Bun monorepo. Read the installed declarations first; when they do not settle an
Fold is an Effect v4 (`4.0.0`) Bun monorepo. Read the installed declarations first; when they do not settle an
API, read `~/projects/effect`, not Effect v3 documentation or examples.

## Fold's architecture
Expand Down
2 changes: 2 additions & 0 deletions .oxlintrc.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@
"effecttsgo/prefer-succeed-some-or-none": "error",
"effecttsgo/prefer-typed-schema-decoder": "error",
"effecttsgo/prefer-unsafe-constructor": "error",
// Effect 4.0.0 marks its ai, http and other moved modules unstable, and fold is built on them.
"effecttsgo/unstable-api-usage": "off",
"effecttsgo/promise-in-effect-success": "error",
"effecttsgo/provide-layer-succeed-to-provide-service": "error",
"effecttsgo/race-first-with-sleep-to-timeout": "error",
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Bun monorepo. Packages live in `packages/*` and run TypeScript directly (no buil
## Stack

- **Runtime / package manager:** Bun (`bun@1.3.14`)
- **Framework:** Effect **v4** (`4.0.0-rc.109`)
- **Framework:** Effect **v4** (`4.0.0`)
- **Testing:** Vitest + `@effect/vitest`
- **Lint / format:** oxlint + oxfmt (120 col, no semicolons)
- **Effect editor tooling:** `@effect/tsgo` with the `@effect/language-service` TS plugin. `tsc` and oxlint are patched
Expand All @@ -32,8 +32,8 @@ packages reference them with `"<pkg>": "catalog:"`. Bump the version in the cata

## Effect v4 source (read this, not v3 docs)

This repo targets **Effect v4 (release candidate)** — its API differs from the widely-documented v3. The v4
source is the `effect` repo:
This repo targets **Effect v4** — its API differs from the widely-documented v3, and from the v4 release
candidates (e.g. `effect/unstable/http` became `effect/http`). The v4 source is the `effect` repo:

- In Riptide worktree tasks it is checked out as a sibling at **`../effect`**.
- Locally it lives at **`~/projects/effect`**.
Expand Down
300 changes: 74 additions & 226 deletions bun.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion examples/alchemy-cloudflare/src/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/
import { AgentFinishedLogEntry, LogEntry, SessionId } from '@humanlayer/fold-core'
import { Effect, Schema } from 'effect'
import { HttpRouter, HttpServerRequest, HttpServerResponse } from 'effect/unstable/http'
import { HttpRouter, HttpServerRequest, HttpServerResponse } from 'effect/http'

import { ChatSessions, Message } from './ChatSessions'

Expand Down
4 changes: 2 additions & 2 deletions examples/alchemy-cloudflare/src/ChatSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import {
Stream,
SynchronizedRef,
} from 'effect'
import { FetchHttpClient } from 'effect/unstable/http'
import { FetchHttpClient } from 'effect/http'

import { bashTool } from './BashTool'
import type { Message, WhenRunning } from './ChatSessions'
Expand Down Expand Up @@ -148,7 +148,7 @@ export default class ChatSession extends Cloudflare.DurableObject<ChatSession>()
Effect.gen(function* () {
const state = yield* Cloudflare.DurableObjectState
// Read from the deploy environment and bound to the Worker as a secret. Missing, the deploy fails.
const apiKey = yield* Config.redacted('OPENAI_API_KEY').pipe(Effect.orDie)
const apiKey = yield* Config.Redacted('OPENAI_API_KEY').pipe(Effect.orDie)
const eventLogs = yield* DurableObjectEventLog
const keepalive = yield* Keepalive
const expiry = yield* SessionExpiry
Expand Down
2 changes: 1 addition & 1 deletion examples/alchemy-cloudflare/src/Worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
import * as Cloudflare from 'alchemy/Cloudflare'
import { Effect } from 'effect'
import { HttpRouter } from 'effect/unstable/http'
import { HttpRouter } from 'effect/http'

import { ChatRoutes } from './Api'
import { ChatSessions } from './ChatSessions'
Expand Down
2 changes: 1 addition & 1 deletion examples/alchemy-cloudflare/src/Workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const REPO_NAME = /^(?!\.{1,2}$)[\w.-]+$/
* `name` under `/workspace` (the URL's last path segment when absent).
*/
export const Repo = Schema.Struct({
url: Schema.String.check(Schema.isStartsWith('https://')),
url: Schema.String.check(Schema.isStartingWith('https://')),
name: Schema.optionalKey(Schema.String),
ref: Schema.optionalKey(Schema.String),
})
Expand Down
4 changes: 2 additions & 2 deletions examples/alchemy-cloudflare/src/WorkspaceFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* `realPath` returns the path unchanged: fold only uses it to key its per-file write lock.
*/
import type { RpcCallError } from 'alchemy'
import { Effect, FileSystem, Option, PlatformError } from 'effect'
import { ByteSize, Effect, FileSystem, Option, PlatformError } from 'effect'

import type { FileInfo, ComputerResult } from './computer/Contract'

Expand Down Expand Up @@ -85,7 +85,7 @@ const toInfo = (info: FileInfo): FileSystem.File.Info => ({
uid: Option.none(),
gid: Option.none(),
rdev: Option.none(),
size: FileSystem.Size(info.size),
size: ByteSize.bytes(info.size),
blksize: Option.none(),
blocks: Option.none(),
})
Expand Down
2 changes: 1 addition & 1 deletion examples/alchemy-cloudflare/test/Api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
type LogEntryInput,
} from '@humanlayer/fold-core'
import { Context, Effect, Layer, Predicate, Ref, Schema, Scope, Stream } from 'effect'
import { HttpRouter } from 'effect/unstable/http'
import { HttpRouter } from 'effect/http'
import { expect } from 'vitest'

import { ChatRoutes } from '../src/Api'
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@
],
"catalog": {
"@kitlangton/terminal-control": "0.3.1",
"alchemy": "2.0.0-beta.76",
"alchemy": "2.0.0-beta.80",
"@cloudflare/computer": "https://github.com/K-Mistele/computer/releases/download/fold-91611593a1d7/cloudflare-computer-0.3.1.tgz",
"@platformatic/vfs": "0.4.0",
"@cloudflare/workers-types": "5.20260929.1",
"@opentui/core": "0.4.3",
"@opentui/keymap": "0.4.3",
"@opentui/solid": "0.4.3",
"solid-js": "1.9.12",
"effect": "4.0.0-rc.112",
"@effect/platform-node": "4.0.0-rc.112",
"@effect/platform-bun": "4.0.0-rc.112",
"@effect/vitest": "4.0.0-rc.112",
"effect": "4.0.0",
"@effect/platform-node": "4.0.0",
"@effect/platform-bun": "4.0.0",
"@effect/vitest": "4.0.0",
"@silvia-odwyer/photon-node": "0.3.4",
"turndown": "7.2.4",
"@types/turndown": "5.0.6",
Expand All @@ -54,7 +54,7 @@
}
},
"devDependencies": {
"@effect/tsgo": "0.47.0",
"@effect/tsgo": "0.48.0",
"@oxlint/plugins": "1.86.0",
"@types/bun": "catalog:",
"@types/node": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion packages/effect-ai-anthropic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ An [Anthropic](https://www.anthropic.com) provider for the Effect AI modules. In
## Installation

```sh
npm install effect@4.0.0-rc.112 @humanlayer/effect-ai-anthropic
npm install effect@4.0.0 @humanlayer/effect-ai-anthropic
```

## Documentation
Expand Down
10 changes: 5 additions & 5 deletions packages/effect-ai-anthropic/UPSTREAM.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ This directory is a vendored source snapshot of the published Effect Anthropic p

| Field | Value |
| ---------------- | ------------------------------------------------------------------------------------------------- |
| Upstream package | `@effect/ai-anthropic@4.0.0-rc.112` |
| Source artifact | `https://registry.npmjs.org/@effect/ai-anthropic/-/ai-anthropic-4.0.0-rc.112.tgz` |
| npm integrity | `sha512-2GQKD3IzfCJuS3DgeuBwUjEYk5iyOqgK5etQkYZ3Ho8IuL8BtGR9d8G5AIiRV0xkNy5DD4vGnDGSHxKWsW2TRg==` |
| Tarball SHA-256 | `4a1bc081405cd7b11e7b4a2f4d1f5eb113b72270ed58043fbd7d104b29e473fc` |
| Imported at | `2026-09-03` |
| Upstream package | `@effect/ai-anthropic@4.0.0` |
| Source artifact | `https://registry.npmjs.org/@effect/ai-anthropic/-/ai-anthropic-4.0.0.tgz` |
| npm integrity | `sha512-/xZYmvRE9yDLKiz1pOIx0CVaB+o0zdJd6sTG9KQbFSkM+Ule0U/6yeN+Q6KD9I7Z73t3WxhK9Xmg1eqU6+IVmQ==` |
| Tarball SHA-256 | `68969de51307457a74aea94273f05f7eab7b570f940ade95bf57b8847fe8e355` |
| Imported at | `2026-10-03` |
| Imported inputs | `src/**`, `README.md`, and `LICENSE` |
| License | MIT; copied to [`LICENSE`](./LICENSE) |

Expand Down
60 changes: 51 additions & 9 deletions packages/effect-ai-anthropic/src/AnthropicClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,27 @@
* authentication headers, API version headers, response decoding, and error
* mapping, then exposes helpers for regular and streaming message requests.
*
* @stability unstable
* @since 4.0.0
*/
import type * as AiError from "effect/ai/AiError"
import * as Array from "effect/Array"
import type * as Config from "effect/Config"
import * as Context from "effect/Context"
import * as Effect from "effect/Effect"
import * as Sse from "effect/encoding/Sse"
import { identity } from "effect/Function"
import * as Headers from "effect/http/Headers"
import * as HttpBody from "effect/http/HttpBody"
import * as HttpClient from "effect/http/HttpClient"
import * as HttpClientError from "effect/http/HttpClientError"
import * as HttpClientRequest from "effect/http/HttpClientRequest"
import type * as HttpClientResponse from "effect/http/HttpClientResponse"
import * as Layer from "effect/Layer"
import * as Predicate from "effect/Predicate"
import * as Redacted from "effect/Redacted"
import * as Schema from "effect/Schema"
import * as Stream from "effect/Stream"
import type * as AiError from "effect/unstable/ai/AiError"
import * as Sse from "effect/unstable/encoding/Sse"
import * as Headers from "effect/unstable/http/Headers"
import * as HttpBody from "effect/unstable/http/HttpBody"
import * as HttpClient from "effect/unstable/http/HttpClient"
import type * as HttpClientError from "effect/unstable/http/HttpClientError"
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"
import { AnthropicConfig } from "./AnthropicConfig.ts"
import * as Generated from "./Generated.ts"
import * as Errors from "./internal/errors.ts"
Expand All @@ -36,6 +37,7 @@ import * as Errors from "./internal/errors.ts"
* Represents the Anthropic client service with methods for the Messages API, including regular and streaming message
* creation.
*
* @stability unstable
* @category services
* @since 4.0.0
*/
Expand Down Expand Up @@ -98,6 +100,7 @@ export interface Service {
* - `content_block_stop`: End of a content block
* - `error`: Error events with type and message
*
* @stability unstable
* @category models
* @since 4.0.0
*/
Expand Down Expand Up @@ -126,6 +129,7 @@ export type MessageStreamEvent =
* @see {@link layer} for providing a client from explicit options
* @see {@link layerConfig} for providing a client from `Config`
*
* @stability unstable
* @category services
* @since 4.0.0
*/
Expand Down Expand Up @@ -155,6 +159,7 @@ export class AnthropicClient extends Context.Service<AnthropicClient, Service>()
* @see {@link layer} for providing an Anthropic client from explicit options
* @see {@link layerConfig} for loading Anthropic client settings from `Config`
*
* @stability unstable
* @category options
* @since 4.0.0
*/
Expand Down Expand Up @@ -216,6 +221,7 @@ const withRedactedHeaders = Effect.updateService(
* @see {@link layer} for providing the client as a `Layer` from explicit options
* @see {@link layerConfig} for providing the client as a `Layer` with `Config`-based settings
*
* @stability unstable
* @category constructors
* @since 4.0.0
*/
Expand Down Expand Up @@ -243,7 +249,41 @@ export const make = Effect.fnUntraced(
: identity
)

const client = Generated.make(httpClient, {
// Let status mapping handle gateway errors without an Anthropic envelope.
const generatedHttpClient = HttpClient.transformResponse(
httpClient,
Effect.flatMap((response) => {
if (
response.status < 400 || response.status >= 500 ||
!response.request.url.endsWith("/v1/messages?beta=true")
) {
return Effect.succeed(response)
}
return Effect.flatMap(Effect.option(response.json), (body) => {
if (
body._tag === "Some" &&
Schema.decodeUnknownOption(Generated.BetaMessagesPost4XX)(body.value)._tag === "Some"
) {
return Effect.succeed(response)
}
return Effect.flatMap(
Effect.orElseSucceed(response.text, () => "Unexpected status code"),
(description) =>
Effect.fail(
new HttpClientError.HttpClientError({
reason: new HttpClientError.StatusCodeError({
request: response.request,
response,
description
})
})
)
)
})
})
)

const client = Generated.make(generatedHttpClient, {
transformClient: Effect.fnUntraced(function*(client) {
const config = yield* AnthropicConfig.getOrUndefined
if (Predicate.isNotUndefined(config?.transformClient)) {
Expand Down Expand Up @@ -366,6 +406,7 @@ export const make = Effect.fnUntraced(
* @see {@link make} for constructing the client service effectfully
* @see {@link layerConfig} for loading client settings from `Config`
*
* @stability unstable
* @category layers
* @since 4.0.0
*/
Expand All @@ -385,6 +426,7 @@ export const layer = (options: Options): Layer.Layer<AnthropicClient, never, Htt
* @see {@link layer} for providing the client from explicit options instead of `Config`
* @see {@link make} for constructing the client service effectfully
*
* @stability unstable
* @category layers
* @since 4.0.0
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/effect-ai-anthropic/src/AnthropicConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import * as Context from "effect/Context"
import * as Effect from "effect/Effect"
import { dual } from "effect/Function"
import type { HttpClient } from "effect/unstable/http/HttpClient"
import type { HttpClient } from "effect/http/HttpClient"

/**
* Service tag for Anthropic client configuration overrides, such as transformations applied to the generated HTTP client.
Expand Down
2 changes: 1 addition & 1 deletion packages/effect-ai-anthropic/src/AnthropicError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export type AnthropicRateLimitMetadata = AnthropicErrorMetadata & {
readonly tokensReset: string | null
}

declare module "effect/unstable/ai/AiError" {
declare module "effect/ai/AiError" {
/**
* Anthropic metadata attached to `RateLimitError` values.
*
Expand Down
Loading
Loading