Skip to content

feat(types): shared session contract package — @codespar/types@0.1.0 (milestone 1) - #7

Merged
dangazineu merged 17 commits into
mainfrom
feature/f4-m1-session-contract
Apr 21, 2026
Merged

dangazineu merged 17 commits into
mainfrom
feature/f4-m1-session-contract

Conversation

@dangazineu

@dangazineu dangazineu commented Apr 21, 2026 •

Copy link
Copy Markdown
Contributor

Extracts shared session types into a standalone zero-dependency package
(@codespar/types@0.1.0) so any runtime — OSS, managed, or custom — can
conform to the same interface without importing the full SDK.

SessionBase is the minimal runtime-agnostic surface (execute, send,
sendStream, connections, close). Session extends it with codespar-managed
capabilities (proxyExecute, authorize, mcp). isCodesparSession() narrows
SessionBase to Session for callers that need the richer API.

tools(), findTools(), and loop() move from session instance methods to free
functions in @codespar/sdk, accepting any SessionBase. This decouples
orchestration logic from the session type and makes it usable across
runtimes. Callers on 0.2.x: see the migration note in packages/core/README.md.

@codespar/managed-agents-adapter@0.1.0 implements SessionBase against the
Anthropic Managed Agents API (pre-GA stub). Key invariants: tool name
injection guard runs before any message is constructed; PolicyHook evaluates
on original params before sanitizeParams; mutex resets in finally so any
throw leaves the session available for the next call.

@codespar/types/testing exports runContractSuite — a Vitest harness that
fires the five SessionBase methods against a live HTTP endpoint. The OSS
engine leg (starting WebhookServer locally and running the suite against it)
is tracked in codespar/codespar#95.

Closes #2, #3, #4, #5, #6.

Adds packages/session-contract/ with package.json, tsconfig.json, and a
stub src/index.ts. The package builds cleanly and is auto-discovered by
the npm workspace glob (packages/*). The ./testing subpath export is
wired with a development condition — implementation follows in subsequent
issues.

Closes #2
All adapter packages declared @codespar/sdk@0.2.0 as devDependency while
the workspace SDK is at 0.2.2. npm was installing the published 0.2.0
from the registry instead of the workspace link, causing typecheck to
resolve against stale types.

Bumps all adapter devDependencies to 0.2.2 so they resolve to the
workspace link and use the current type definitions.
…guard

Introduces the @codespar/session-contract package with two interface layers:

- SessionBase: runtime-agnostic (id, status, execute, send, sendStream,
  connections, close) — usable by any MCP-compatible runtime
- Session extends SessionBase: codespar-specific additions (proxyExecute,
  authorize, mcp?)

The SDK's own Session type extends the contract's Session with higher-level
helpers (tools, findTools, loop) that will migrate to free functions in the
next release, preserving backwards compatibility across all 12 adapter
packages in the interim.

Also adds isCodesparSession(s: SessionBase): s is Session type guard for
use in multi-runtime agents that receive a SessionBase and need to access
codespar-specific methods.

- Add null-guards to @codespar/mcp (mcp is optional on Session)
- Fix status literal narrowing in all adapter fakeSession test mocks
Completes the session-contract wiring across the entire SDK:

- @codespar/sdk now declares @codespar/session-contract as a runtime
  dependency and re-exports all contract types via export * so callers
  keep their existing import paths unchanged
- loop(), tools(), and findTools() are promoted from Session interface
  methods to free functions (packages/core/src/loop.ts and tools.ts)
  that accept SessionBase, making them usable with any conforming runtime
- @codespar/mcp getMcpConfig and getClaudeDesktopConfig now guard against
  sessions that do not expose an MCP transport endpoint
- All 12 adapter packages updated to call tools(session) (the free
  function) instead of session.tools()
- fakeSession mocks across all adapter test packages cleaned up to reflect
  the narrower Session interface

Breaking change: session.mcp is now typed as optional. Any caller that
accessed session.mcp.url or session.mcp.headers without a null-guard will
get a TypeScript compile error. Add an explicit check (if (!session.mcp))
before accessing the property.

Version bumps: @codespar/sdk 0.3.0, @codespar/mcp 0.3.0, all 12 adapter
packages 0.3.0.
Adds runContractSuite(baseUrl, apiKey) in the /testing subpath of
@codespar/session-contract. The suite registers vitest test cases that
validate the full SessionBase behavioral contract against any live backend:
streaming event shapes, execute/ToolResult, send/SendResult, connections,
and close/status transition.

Key design choices:
- InvalidBaseUrlError is thrown synchronously before any fetch so
  misconfigured CI environments fail at registration time, not mid-run
- Only https:// URLs and localhost are accepted to prevent API key
  exfiltration to arbitrary hosts
- The suite builds a minimal raw-fetch SessionBase internally so consumers
  can call runContractSuite with just a base URL and key
- afterEach closes the session in a try/finally block so cleanup runs even
  when an assertion fails

contract-managed.test.ts is gated with describeIf on CONTRACT_API_KEY so
the managed leg is skipped (exit 0) in environments without backend access.

The managed CI step is added to ci.yml in commented-out form. All four
backend prerequisites documented in the comments must be confirmed before
the step is enabled.
Adds packages/managed-agents-adapter/ — a bridge between the Anthropic
Managed Agents SDK (pre-GA stub) and the codespar SessionBase interface.
Any caller typed against SessionBase works unchanged regardless of which
runtime backs the session.

Key design choices and safety properties:

Tool name validation (/^[a-zA-Z0-9_-]+$/) runs before any message is
constructed so whitespace or newline characters cannot inject instructions
into the JSON payload sent to the Managed Agents API.

PolicyHook evaluation runs on the original params BEFORE sanitizeParams is
applied. Reversing the order would let a caller strip fields (e.g. amount)
that the policy uses to enforce fund-transfer caps.

execute(), send(), and sendStream() each acquire a mutex (Promise) and
reset it to null in a finally block. Without the finally, a single rejected
operation permanently blocks the mutex and every subsequent call hangs.

DrainTimeoutError JSDoc explicitly states that callers must not auto-retry
commerce tools (Pix transfers, NF-e issuance) after this error — the remote
operation may have already executed.

AgentRuntime is a local stub interface (pre-GA) with a comment marking the
import path to switch to at Anthropic Managed Agents SDK GA.

Five error classes: InvalidToolNameError, PolicyViolationError,
ApprovalRequiredError, ConcurrentOperationError, DrainTimeoutError.
@dangazineu
dangazineu marked this pull request as ready for review April 21, 2026 05:55
^0.2.0 caps at <0.3.0 so npm resolved the published 0.2.2 into each adapter's
local node_modules instead of using the workspace package. Bumping to ^0.3.0
aligns the peer constraint with the workspace version, so npm hoists the
workspace symlink and no per-package registry copy is installed.
…and policy ordering

Covers the three behaviours that are otherwise unverifiable at CI time:
- Tool name injection guard (valid/invalid names, error carries offending name)
- Mutex lifecycle: ConcurrentOperationError while in-flight, mutex released in
  finally so the next call goes through after any throw (execute, send, sendStream)
- Policy evaluation order: policyHook runs on original params before sanitizeParams,
  and the runtime receives only the sanitized payload
- DrainTimeoutError: thrown when the event stream does not yield a tool_result within
  the configured deadline; error carries the configured timeout value
- Happy-path coverage for execute, send, sendStream, connections, and close
- sendStream returns AsyncIterable not AsyncGenerator; access the iterator
  via [Symbol.asyncIterator]() before calling .next()
- StreamEvent.type is a closed union so comparing to a literal outside the
  union is a type error; replace with an exact array assertion instead
…me guide

- packages/session-contract/README.md: documents SessionBase, Session,
  isCodesparSession, wire types, and runContractSuite usage
- packages/managed-agents-adapter/README.md: documents createManagedAgentsSession,
  PolicyHook, all error classes, and the pre-GA AgentRuntime stub
- docs/custom-session-runtime.md: guide for implementing a custom SessionBase
  runtime — per-method walkthrough, duck-typing for tools(), conformance testing
  with runContractSuite, narrowing to Session with isCodesparSession
- README.md: add session-contract and managed-agents-adapter to packages table;
  fix loop example to use the loop() free function
- packages/core/README.md: replace session.tools/findTools/loop instance method
  entries with the free function equivalents; add sendStream and proxyExecute
@codespar/types is more idiomatic for a zero-dep TypeScript interface package.
The -contract suffix carries enterprise Java connotations that read oddly in
the npm ecosystem. The package covers all shared wire types (ToolResult,
SendResult, StreamEvent, etc.) — not just session interfaces — so the broader
name is accurate.

Renames packages/session-contract/ -> packages/types/, updates all imports,
package.json references, README links, and regenerates the lock file.
contract-managed.test.ts targeted api.codespar.dev (the enterprise managed
backend) by default. A public MIT repo must not reference or depend on private
infrastructure — external contributors can't reach it, and an enterprise outage
would break the public CI badge.

runContractSuite in @codespar/types/testing stays: it's a generic conformance
harness useful to anyone building a custom SessionBase runtime. The test that
wires it to the managed backend belongs in codespar-enterprise's CI instead.
Private reference cleanup (touched files):
- Remove codespar-enterprise path and api.codespar.dev from session.ts/index.ts
  file headers — those files were edited in this PR and should have been cleaned
- Replace api.codespar.dev with a neutral example URL in mcp test fixtures
- Replace api.codespar.dev example in contract-suite.ts JSDoc (ships in npm)
- Clean up api.codespar.dev reference in sdk test file comment

Dependency pinning:
- Pin @codespar/types to "^0.1.0" in @codespar/sdk (was unbounded "*")

Documentation correctness:
- Fix AuthConfig field: redirectUrl → redirectUri; AuthResult: authUrl → authorizeUrl
- Fix runContractSuite endpoint docs: actual wire format is Bearer auth + {servers,user_id}
  body, not {apiKey} body; clarify Vitest-only (not Jest)
- Remove LoopStep.server from README examples — field does not exist on the type
- Clarify that tools() duck-typing is an internal mechanism, not a stable extension point
- Add 0.2.x → 0.3.0 migration note to @codespar/sdk README

Test coverage:
- Add tools.test.ts: covers tools() empty fallback, delegation, findTools() name/
  description matching, multi-match, no-match, empty-session cases
- Add cross-method ConcurrentOperationError tests: send() while execute() in flight
  and execute() while send() in flight
- Add close()-while-in-flight test: verifies mutex drain before status transitions
@dangazineu dangazineu changed the title feat(session-contract): shared session contract package (milestone 1) feat(types): shared session contract package — @codespar/types@0.1.0 (milestone 1) Apr 21, 2026
@dangazineu
dangazineu merged commit 2575f6a into main Apr 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(session-contract): scaffold @codespar/session-contract package

1 participant