Skip to content

Releases: polymind-inc/agent-framework-js

v0.4.0

Choose a tag to compare

@shibayan shibayan released this 28 Aug 10:10
Immutable release. Only release title and notes can be modified.
76dfe52

A hardening and consolidation release: ten breaking changes tighten types, credentials, telemetry
and the supported surface against the reference implementations — Foundry configuration
centralizes in FoundryProject, GenAI message events move to the chat span with v1.36.0
bodies, and the provider request-assembly helpers leave the supported core surface — alongside
safety fixes across streaming, serialization, hosting and the release pipeline.

  • [BREAKING] @polymind-inc/agent-framework-core — setIfDefined, withoutUndefined,
    topLevelMediaType and arrayToStream are no longer exported from the root entry. They assemble
    a request payload for a specific provider SDK — an implementation detail of writing a
    ChatClient, not part of the programming model — and none of the reference implementations
    publishes a counterpart in this shape. They now live on the /internal subpath the framework's
    own packages share, which carries no compatibility promise. Each is under ten lines and inlines
    directly: setIfDefined/withoutUndefined drop the entries whose value is undefined,
    topLevelMediaType lowercases the part of a media type before the /, and arrayToStream
    yields an array's items from an async generator.
  • [BREAKING] @polymind-inc/agent-framework-core — the MiddlewareKind type is removed. It
    was a free-standing alias nothing in the framework consumed; the discriminant lives on the
    middleware objects themselves, so Middleware['kind'] expresses the same type where one is
    needed.
  • [BREAKING] @polymind-inc/agent-framework-agentserver — the TERMINAL_EVENT_TYPES
    constant is no longer exported. The TerminalEventType type and the isTerminalEventType
    predicate remain: use the predicate to test an individual event type. Code that enumerated or
    iterated the exported tuple has no direct replacement.
  • @polymind-inc/agent-framework-core — a new /internal subpath holds the utilities the
    framework's own packages share. Like the other /internal entries, it is a
    contract between the framework's packages and not part of the supported surface.
  • @polymind-inc/agent-framework-anthropic — a replayed function_call whose string
    arguments parse to a JSON array or scalar no longer reaches the API as tool_use.input; it
    degrades to {} like every other non-object payload, keeping input the JSON object the
    Messages API requires.
  • [BREAKING] @polymind-inc/agent-framework-foundry — Foundry credentials and the project
    endpoint are centralized in the new FoundryProject handle
    (new FoundryProject(endpoint, credential, { scope?, fetch? }), mirroring
    new AIProjectClient(endpoint, credential) from @azure/ai-projects: both arguments are
    required, and neither is resolved from the environment or constructed implicitly).
    FoundryChatClient, FoundryMemoryProvider, FoundryToolbox and FoundryResponseStore take a
    required project instead of the removed per-component credential, scope and
    projectEndpoint options — previously each component silently constructed its own
    DefaultAzureCredential chain, and a stale FOUNDRY_PROJECT_ENDPOINT could silently decide
    where bearer tokens were sent. Every component built from one project shares one per-scope
    bearer-token cache. The tokenProvider export is removed; project.getToken() is its
    successor. The one remaining implicit default is inside the hosting bootstrap: a hosted
    container's default response store still assembles its project from the platform-injected
    endpoint and DefaultAzureCredential, the same default the Python agent server's Foundry
    storage applies. The reference implementations all demand this explicitness: .NET and Python
    hang every Foundry feature off an explicitly constructed AIProjectClient (Python requires
    credential whenever no project_client is given), and Go takes a credential as a required
    constructor argument.
  • [BREAKING] @polymind-inc/agent-framework-core — ResponseBase<T>.value is now
    T | undefined. A suspended run (for example, one waiting for tool approval) and a response
    created without a structured-output value have always carried undefined at runtime; callers
    must now narrow response.value before reading it. The response factories and update-folding
    helpers no longer claim a value exists when none was supplied.
  • [BREAKING] @polymind-inc/agent-framework-core — a raw JSON Schema whose root is not an
    object no longer types a tool or skill-script input as Record<string, unknown>; its input is
    unknown. Object-root schemas retain the existing record inference. Raw schemas are now
    validated locally before executable tools and skill scripts run, rather than relying on a model
    provider to enforce them.
  • [BREAKING] @polymind-inc/agent-framework-anthropic — thinking is a discriminated union:
    { type: 'enabled', budgetTokens } requires a safe-integer budget of at least 1024, while
    { type: 'disabled' } cannot carry a budget. Invalid configurations now fail at construction
    instead of reaching Anthropic as an invalid request.
  • [BREAKING] @polymind-inc/agent-framework-foundry — FoundryTarget now enforces exactly one
    of its two selectors. When an already-configured SDK client is supplied, baseURL reports the
    client's actual URL.
  • [BREAKING] @polymind-inc/agent-framework-foundry — FoundryTarget's modelDeployment
    selector is renamed model, and the exported guard isModelDeployment is renamed
    isModelTarget. The value still names a model deployment in the project; model is what both
    .NET (the model parameter on FoundryAgent and AsAIAgent) and Python (the model keyword,
    FOUNDRY_MODEL) call it, while Go's ModelDeployment is a per-mode type name in a design
    TypeScript's discriminated union does not share.
  • [BREAKING] @polymind-inc/agent-framework-core — with sensitive-data capture enabled, the
    per-message GenAI telemetry events (gen_ai.system.message, gen_ai.user.message,
    gen_ai.assistant.message, gen_ai.tool.message, gen_ai.choice) are emitted on the chat
    span only, with the OTel GenAI v1.36.0 structured bodies carried as JSON on the body
    attribute. Previously every exchange was double-reported — the same events also fired on the
    invoke_agent span — and the payload was a role+parts serialization matching neither semconv
    generation; the reference implementations emit message events for the model invocation only.
    The invoke_agent span keeps attribute-form content (gen_ai.input.messages /
    gen_ai.output.messages are unchanged on both span kinds). Every event carries gen_ai.system,
    a response without a finish reason emits no gen_ai.choice, and event timestamps step one
    microsecond apart so ordering survives backends that collapse tight timestamps. Consumers
    reading message events off invoke_agent spans, or parsing the previous content attribute
    payload, must switch to the chat span's events and the JSON body attribute.
  • Security: approval requests are immutable snapshots before they reach callers, so mutating a
    streamed or awaited request cannot change the arguments later executed after approval. Raw JSON
    Schema arguments are checked locally, executable-only approval bypassing no longer consumes
    declaration-only calls, and reused function call ids are correlated by logical occurrence.
  • ResponseStream.finalResponse() now shares concurrent source initialization and draining, so
    repeated concurrent calls cannot duplicate model/tool work or leak an iterator. Middleware
    cancellation before the first pull and update-hook failures now finalize the run consistently.
  • A streamed run interrupted under allowBackgroundResponses no longer double-stores its
    exchange. A streaming run that ends suspended skips history persistence entirely — its
    continuation token replays the caller's input and every update already produced — so the run
    that finally completes appends the whole exchange exactly once, with partial messages merged by
    the fold rather than split across store entries. Previously the suspended run and the resumed
    run each persisted the input and the partial updates, so the next turn replayed the question
    and the partial answer twice, violating the documented HistoryProvider contract
    (saveMessages is handed only the messages new to that turn). Awaited suspensions are
    unchanged: their tokens carry nothing, so each half stores its own fragment. The fold takes the
    continuation token from the latest update, so a background stream that runs to completion
    persists normally.
  • Serializing content now sanitizes Content values nested in function result fields, while
    deserialization leaves plain tool-result JSON untouched so it round-trips exactly. Future content types that
    carry the userInputRequest marker suspend structured-output parsing without requiring a core
    release for each new discriminator.
  • OpenAI stream error events and incomplete terminal errors are surfaced as error content, unknown
    message parts survive awaited and streamed session round trips, and the final transformed
    store request option is the value retained for follow-up requests.
  • Anthropic streaming drops unknown delta fragments that cannot be replayed as content blocks,
    preserves unknown complete blocks, maps citations to framework annotations, and treats MCP
    authorization headers case-insensitively.
  • A2A task conversion now preserves terminal status messages, falls back to the agent history only
    for a terminal task that produced no artifacts (never duplicating a message the status already
    carries), and de-duplicates artifacts already emitted by the stream. Resuming with a
    continuation token needs the token alone; a session passed alongside is updated from the
    resumed task.
  • Agent Server and Foundry hosting now...
Read more

v0.3.0

Choose a tag to compare

@shibayan shibayan released this 13 Aug 03:27
Immutable release. Only release title and notes can be modified.
6505ba0

The first minor since 0.2, and the first release after the public-API surface freeze was lifted:
six breaking changes settle the naming conventions and correct semantics against the reference
implementations, alongside Agent Skills, the Foundry Memory provider, the Invocations protocol,
and file-backed persistence for transcripts and skills.

  • [BREAKING] @polymind-inc/agent-framework-openai — the OpenAIChatClientOptions
    interface is renamed OpenAIChatClientConfigBase. It is the construction-time half of
    OpenAIChatClientConfig, and the package convention names construction types *Config and
    per-call types *Options (OpenAIChatOptions is unchanged); OpenAI was the one provider whose
    construction type broke that rule. OpenAIChatClientConfig itself — what new OpenAIChatClient(…)
    accepts — is unchanged, so only code that names the base interface directly is affected.
  • [BREAKING] the MCP acronym in public identifiers is now spelled Mcp, matching the official
    @modelcontextprotocol SDK and this framework's own newer API (McpConnection,
    mcpSkillsSource(), withMcpClientSpan). The wire format is unaffected. Renames —
    @polymind-inc/agent-framework-mcp: MCPClient → McpClient, MCPClientConfig →
    McpClientConfig; @polymind-inc/agent-framework-core: MCPToolOptions → McpToolOptions,
    SupportsMCPTool → SupportsMcpTool, supportsMCP → supportsMcp, and the capability method
    getMCPTool → getMcpTool (the OpenAI, Anthropic and Foundry clients implement the renamed
    method). There are no deprecated aliases; update imports and any custom ChatClient that
    declares the MCP capability.
  • [BREAKING] @polymind-inc/agent-framework-agentserver — resolveUnder and
    validatePathSegment are no longer exported from the package's main entry. They are path-safety
    primitives for file-backed stores, not part of the server protocol surface (the reference
    implementation exposes only the domain-scoped state-root helper, which stateRoot continues to
    mirror). The framework's own Foundry hosting adapter now reaches them — together with the shared
    atomic JSON file helpers readJsonFile / writeJsonFile, previously duplicated between the two
    packages — through the new ./internal subpath, an internal contract like
    @polymind-inc/agent-framework-openai/internal whose exports may change in any release.
  • [BREAKING] @polymind-inc/agent-framework-agentserver — HandlerContext gains two required
    fields: agentReference (the resolved agent this turn targets, always with a non-empty name)
    and agentSessionId (the resolved sandbox session id, as returned on x-agent-session-id).
    Handlers only read the context and are unaffected; code that constructs HandlerContext
    values — test doubles, custom protocol frontends — must now supply both.
  • [BREAKING] @polymind-inc/agent-framework-core — the function-calling loop no longer
    hardcodes OpenAI's conv_ prefix when deciding whether a conversation id advances between tool
    rounds. Which ids are stable service-side anchors is now the provider's declaration:
    ChatClientMetadata.stableConversationId, a new optional predicate the loop and the agent's
    session propagation consult. The built-in OpenAI, Azure OpenAI and Foundry clients declare it,
    so their behavior is unchanged. A custom ChatClient implementation that relied on the loop
    pinning conv_… ids must now declare the predicate on its metadata
    — without it, every
    conversation id a round reports advances the chain, matching the .NET and Python loops.
  • [BREAKING] @polymind-inc/agent-framework-foundry — FoundryChatClient no longer asks for
    reasoning.encrypted_content implicitly. Not every Foundry deployment supports encrypted
    reasoning, and one that does not rejects a request that asks for it — which the client did on
    every call without service-side storage, with no way to turn it off. A caller whose deployment
    does support encrypted reasoning, and who replays the transcript from their own side, must now
    list reasoning.encrypted_content in options.include
    ; without it a reasoning model's
    replayed transcript fails for missing reasoning content. Matches the upstream Python fix and the
    function-calling loop specification, which make the request an explicit caller opt-in on
    Foundry.
  • @polymind-inc/agent-framework-core — new Agent Skills support
    (#21): skillsProvider() is a
    ContextProvider that advertises each available skill's name and description in the system
    prompt and registers the three tools the model uses to pull one in on demand — load_skill,
    read_skill_resource and run_skill_script. Skills are declared in code with inlineSkill()
    (with skillResource() and skillScript(), the latter taking a Standard Schema it also
    validates against), or built from a SKILL.md document with markdownSkill();
    parseSkillMarkdown() exposes the frontmatter parser on its own. Sources come from
    inMemorySkillsSource() or any object implementing SkillsSource, and compose through
    aggregateSkills(), filterSkills(), deduplicateSkills() and cacheSkills(). Skills passed
    to the provider directly are cached and deduplicated for you; a source you supply is used exactly
    as given, because caching one that varies per agent or tenant would replay one run's skills for
    another. Every skill tool requires approval by default — approvals relaxes it per tool, and a
    toolApprovalMiddleware rule over the SKILL_TOOL_NAMES constant can reimpose it selectively
    on a relaxed tool (a middleware 'allow' cannot bypass a tool's own approval requirement). A skill or
    resource the model asks for and does not exist comes back as a message it can correct rather than
    a failed run; a skill a source cannot load is skipped and reported through onSkillError.
    Walking a directory of SKILL.md files is not part of the core, which has no filesystem;
    directorySkillsSource on the /node subpath covers it.
  • @polymind-inc/agent-framework-mcp — new mcpSkillsSource(), also reachable as
    McpClient.skillsSource(): discovers the Agent Skills an MCP server publishes by reading the
    well-known skill://index.json catalogue, fetching each SKILL.md body and any document it
    refers to only when the model asks. McpConnection.readResource() is exposed for it, with the
    same reconnect-once behaviour as callTool and a resources/read client span. A server with no
    catalogue contributes no skills; an index entry the framework cannot use — an archive skill, a
    malformed name — is skipped and named, while a server that refuses the request surfaces the
    failure rather than being read as an empty catalogue.
  • @polymind-inc/agent-framework-foundry — FoundryToolbox now serves the toolbox's Agent
    Skills as well as its tools: asSkillsProvider() returns a ready-made provider (and
    skillsSource() the source alone) over the connection the tools already use, so discovery
    carries the same per-call Entra token and x-agent-foundry-call-id, and a CONSENT_REQUIRED
    refusal arrives as the same typed ToolboxConsentRequiredError. The new loadTools: false
    option hides the toolbox's tools entirely — the gateway is never asked to list them — for an
    agent that wants only its skills.
  • @polymind-inc/agent-framework-foundry — new FoundryMemoryProvider: a ContextProvider
    backed by a Microsoft Foundry Memory Store
    (#25). It searches the store
    before each run — once per session for the user's profile memories, then per turn for memories
    relevant to the input — injects what it finds as a single user message under a configurable
    context prompt, and sends the completed turn back for extraction afterwards. A failed run is not
    stored. Memories are partitioned by a required scope, given as a value or as a function of
    the run and pinned to the session on first use, so one provider instance can serve every user of
    a hosted container; hostedUserScope() (exported from /hosting) resolves it from the platform
    user id of the turn. Service failures default to failureMode: 'continue' — the run proceeds
    without the memories — and 'throw' fails the run instead; onFailure observes both. The
    provider also carries the store operations a caller needs around it:
    ensureMemoryStoreCreated, getMemoryStore, deleteStoredMemories and whenUpdatesCompleted.
    The transport is fetch against the preview memory-store routes (Foundry-Features: MemoryStores=V1Preview) and is overridable, so tests need no live credentials.
  • @polymind-inc/agent-framework-agentserver — new InvocationsServer: the Foundry
    Invocations protocol (#29),
    served alongside the existing Responses protocol. The protocol prescribes no payload — the
    request body reaches the InvocationHandler unread, and the handler's Response is what the
    caller gets — while the server owns the routes (POST /invocations, GET /invocations/{id},
    POST /invocations/{id}/cancel, /readiness), invocation and session id resolution
    (x-agent-invocation-id; agent_session_id query → FOUNDRY_AGENT_SESSION_ID → generated),
    their echo on every response including errors, cancellation via InvocationContext.signal,
    opaque upstream-classified handler failures, SSE keep-alive injection for
    text/event-stream bodies, and W3C trace propagation with the
    azure.ai.agentserver.invocation_id / .session_id baggage. serve (on /agentserver/node)
    now accepts any { fetch, drain } protocol server — a widening, existing callers are
    unaffected.
  • @polymind-inc/agent-framework-foundry — new InvocationsHostServer (on `/hosti...
Read more

v0.2.2

Choose a tag to compare

@shibayan shibayan released this 09 Aug 09:32
Immutable release. Only release title and notes can be modified.
5bc0bb5

Fixes accumulated since 0.2.1, centred on running agents as Microsoft Foundry Hosted Agents:
persistence against the Foundry storage service, latency of streamed model calls, transcript
fidelity, and telemetry attribution.

  • @polymind-inc/agent-framework-openai
    • A streamed Responses API call now releases its SSE stream as soon as the terminal event
      (response.completed / response.incomplete / response.failed) has been yielded, instead
      of draining to the connection close. Azure AI Foundry's /openai/v1/responses holds the
      socket open for ~5 seconds after the terminal event and never sends the [DONE] sentinel, so
      every streamed round paid that tail — for a deployed hosted agent, the measured end-to-end
      turn dropped from 9.7s to 3.5s. Against an endpoint that closes promptly after the terminal
      event this is a no-op. The wrapper is a workaround for the service-side behavior; its removal
      is tracked in #40.
    • A failed response now surfaces its diagnostic: the parsed response and the response.failed
      stream event carry an error content built from response.error, with generic substitutes
      when the wire carries no usable message or code. Previously the reason a run failed was only
      available in rawRepresentation.
    • A local tool's approval no longer reaches the Responses API input. Only hosted (MCP)
      approvals serialize to mcp_approval_request / mcp_approval_response items; a local
      approval is resolved in-process, and serializing it produced an orphaned request
      (server_label: null) or a response referencing an id the API never issued. Mirrors the
      upstream Python fix.
  • @polymind-inc/agent-framework-core
    • AgentResponse.text / ChatResponse.text join the texts of multiple messages with a
      newline, matching the .NET reference (previously concatenated without a separator).
    • gen_ai.system_instructions is recorded on chat spans only when sensitive-content capture is
      enabled, serialized as a parts array ([{"type":"text","content":…}]). It was previously
      stamped unconditionally as a bare string, so the system prompt reached the tracing backend
      even with capture off — the OpenTelemetry GenAI conventions treat the attribute as opt-in,
      and the .NET and Python implementations gate it the same way.
  • @polymind-inc/agent-framework-foundry
    • FoundryResponseStore is production-ready and is now the hosted default. Writes carry the
      resolved agent_reference and forward the platform call id (the two undocumented
      requirements behind the service's opaque 500s), replayed history travels as item-id
      references instead of re-sent items, conversation ids resolve through the service's own
      linkage, ambiguous failures are retried with bounded backoff and reconciled against what the
      service actually holds, and background responses replay from a sandbox-local event mirror —
      removing the documented 501 limitation on background execution. A hosted container without a
      reachable project endpoint falls back to the sandbox filesystem.
    • The hosting converters cover the Responses v2 item set: twelve more inbound item types —
      provider-run searches, code interpreter and image generation among them — now convert to
      framework messages instead of being silently dropped from the replayed transcript, and the
      output builder emits the matching wire representations so those items survive into the next
      turn's history. Semantics cross-checked against the .NET and Python hosted converters.
  • @polymind-inc/agent-framework-agentserver
    • An input item that arrives without an id is assigned a platform id under its type's prefix
      before persistence, covering the reference id generator's full per-type dispatch. The Foundry
      storage service refuses an id-less item with an opaque 500, so a turn whose input was an
      item array without ids — the Foundry Playground's request shape among them — ended in
      response.failed with storage_error instead of completing. An item that already carries an
      id keeps it, and an id-less item of an unrecognized type is left out of persistence.
    • GET /responses/{id} reports cancelled as soon as a cancel has been accepted, instead of
      in_progress for as long as the cancel waits out its grace period. Only the status is
      overridden; the cancelled terminal still clears the output when the winddown ends, matching
      the reference server's refresh.
    • The hosted observability setup instruments outbound fetch (undici), so model requests and
      Foundry storage writes appear as HTTP client spans nested under the chat / invoke_agent
      spans — a 7-second span now shows where the time went.
    • Hosted telemetry is attributed to the deployed agent: a processor stamps the
      microsoft.gen_ai.main_agent.* attributes (the only microsoft.* span attributes the Azure
      Monitor JS exporter forwards), and the resource carries foundry.* attributes mirroring the
      .NET host, so the Foundry portal can associate spans with the agent and project.
    • New public API from the storage work: stateRoot and resolveAgentReference are exported,
      and FoundryResponseStoreConfig accepts replayRoot and retry.
  • Repository: issue and pull request labels are applied automatically from paths and templates.

v0.2.1

Choose a tag to compare

@shibayan shibayan released this 06 Aug 03:32
Immutable release. Only release title and notes can be modified.
bccec3b

Documentation and positioning only — no change to any published API.

  • @polymind-inc/agent-framework is now the single supported surface. All examples, the
    repository README and every code sample import through the main package
    (@polymind-inc/agent-framework, …/openai, …/foundry/hosting, …). The
    @polymind-inc/agent-framework-* constituent packages remain published — the main package
    depends on them at exact versions, and importing one directly still resolves to the same
    modules — but their READMEs now state that the main package is the documented way in. This
    removes the awkwardness of two equivalent import styles for the same code.

v0.2.0

Choose a tag to compare

@shibayan shibayan released this 05 Aug 04:31
Immutable release. Only release title and notes can be modified.
a9838e9

One new package; no change to any existing published API.

  • @polymind-inc/agent-framework — new umbrella package, mirroring the Python
    agent-framework distribution. It depends on every granular package and re-exports each
    under a subpath: the root entry is the core, and /testing, /openai, /anthropic, /mcp,
    /a2a, /foundry, /foundry/hosting, /agentserver, /agentserver/node and
    /agentserver/observability map one-to-one onto the packages they re-export. The granular
    packages are unchanged and remain the smaller install.
  • @polymind-inc/agent-framework-agentserver — routing no longer trims the trailing slash of a
    request path with a backtracking regular expression. The old pattern cost time quadratic in the
    length of a run of slashes, so an unauthenticated GET on a path such as /////…/a could hold
    the event loop for seconds.
  • @polymind-inc/agent-framework-core — generated ids (Agent.id, AgentSession.sessionId,
    the messageId of framework-emitted messages) always come from crypto.randomUUID. They
    previously fell back to Math.random on a runtime without it, which is not a suitable source for
    an identifier. A runtime that does not provide crypto.randomUUID — a browser on a page served
    over plain HTTP — now throws instead of producing a guessable id.

v0.1.1

Choose a tag to compare

@shibayan shibayan released this 04 Aug 00:18
Immutable release. Only release title and notes can be modified.

Metadata only — no code changes, and no change to any published API. If you are already on 0.1.0, there is nothing here that affects your code.

The repository moved to polymind-inc/agent-framework-js. The Agent Framework names a language implementation that lives outside the .NET/Python monorepo with a language suffix on its own repository — compare microsoft/agent-framework-go — so the previous name both read as a fork of microsoft/agent-framework and pointed away from where this would land if it were adopted upstream.

The repository, homepage and bugs fields of all seven packages now point at the new location. 0.1.0 shipped with the previous one, which only resolves through a GitHub redirect.

Install

npm install @polymind-inc/agent-framework-core @polymind-inc/agent-framework-openai

Packages

All seven are versioned and released in lockstep, and published with npm provenance:

Package Purpose
@polymind-inc/agent-framework-core Agent, AgentSession, the Message/Content model, tool(), middleware, the ChatClient seam
@polymind-inc/agent-framework-openai OpenAI and Azure OpenAI (Responses API)
@polymind-inc/agent-framework-anthropic Anthropic (Messages API)
@polymind-inc/agent-framework-mcp Model Context Protocol client
@polymind-inc/agent-framework-a2a Agent2Agent (A2A) protocol client
@polymind-inc/agent-framework-foundry Microsoft Foundry chat client and Hosted Agent hosting
@polymind-inc/agent-framework-agentserver Foundry Responses container protocol v2.0.0 server

Requires Node.js 24 or newer. ESM only.

Workflows are designed but not implemented — see the README for the full list of known limitations, and CHANGELOG.md for the history.

This is an independent community implementation. It is not an official Microsoft product, and it is not affiliated with or endorsed by Microsoft.

v0.1.0

Choose a tag to compare

@shibayan shibayan released this 03 Aug 16:58
Immutable release. Only release title and notes can be modified.

Initial public release. See CHANGELOG.md.