@zephyrcloudio/harness-llm

Portable Effect Schema and Stream contracts for one provider model attempt.

The package owns canonical instructions and messages, model-visible tool descriptors, per-request generation and provider options, assistant-message-bound private continuation artifacts, exact attempt usage with optional provider billing provenance, ordered model events, typed failures, and the scoped Model service. Provider adapters lower these values to their own wire formats.

It does not execute tools, retry requests, mutate history, persist data, run the model/tool continuation loop, or decide Turn outcomes. The scripted model is a deterministic test provider and uses only Effect synchronization primitives.

Production Turns use this contract through the Core-owned continuation loop. Provider-specific wire values stay inside their native provider adapters.

Stateful providers use openModelRequestChain to allocate one scoped chain per Turn. The chain is reused for retries and model/tool rounds, while request-chain state stays off the reusable service.

ModelService.stream MUST return its declared Stream synchronously. It must not be async or return a Promise, thenable, or Proxy wrapper. Put asynchronous provider work inside the returned Stream. Malformed async returns are unsupported trusted-code behavior. Ordinary Promise rejection observation is best effort and is not a security boundary. Portable JavaScript cannot guarantee observation for Proxy-hidden Promises or Promises with hostile Symbol.species behavior whose constructor cannot be reversibly shadowed. A typed Turn outcome is not guaranteed. Under Node's strict unhandled-rejection behavior, the process may terminate and the raw rejection reason may appear in process diagnostics.

Native provider authoring contract

defineProvider defines a native LLM provider module. A native provider module exports its defineProvider(...) value as a named export (default exports are not used):

  • definitionId: stable lowercase branded string matching /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/.
  • version: author-owned version string.
  • optionsSchema: Effect Schema codec for provider options.
  • credentials: optional Effect Schema codec for private persisted credentials (e.g. API keys).
  • requestOptionsSchema: Effect Schema codec for per-request options.
  • filterModel: optional deterministic capability/protocol filter for catalog models.
  • correctModel: optional narrow corrections for catalog models (upstream ID, limits, etc.).
  • acquireModel: scoped synchronous Effect returning the ModelService for execution. All callbacks must be synchronous (no async / Promise): acquireModel returns its Effect, filterModel a boolean, correctModel the corrected metadata. Core decodes options once per setup with the definition's schema, so acquireModel receives already-validated options. credentials resolve live at each acquisition (stored/configured/env) and arrive as opaque Redacted values — validate emptiness defensively, as the compatible provider does. It constructs the ModelService directly instead of building a throwaway Layer per Turn. The acquisition input also carries the exact upstream model id, optional per-model API root, and selected wire protocol (chat, responses, or messages). Providers with a fixed protocol may ignore the protocol; a multi-protocol provider dispatches on it instead of hardcoding model lists.

Declarative compatible providers

makeCompatibleProviderDefinition builds one native provider definition from validated data. It uses Ze's Chat Completions, Responses, and Messages adapters directly; no AI SDK sits between Ze and the upstream API. The definition can configure routes, bearer/header/no authentication, protocol-specific authentication and headers, static headers, Session-derived headers, and typed failure classification. Core combines that transport with catalog-selected model protocols, endpoints, upstream IDs, and body defaults.

Chat and Responses authenticate with bearer tokens by default. Messages uses x-api-key by default. An explicit authentication rule overrides these defaults. Unsupported protocols are rejected by the schema rather than routed to a fallback wire.

Provider base URLs

The protocol adapters share one base URL contract (providers/base-url.ts). A configured base URL must be an absolute http(s) URL without credentials, query, or fragment, including bare ?/# delimiters. Query and fragment are rejected rather than stripped or preserved: silently rewriting the configured root would change proxy routing without telling the operator, and preserving them would forward ambient URL material into every provider request. Endpoint paths are joined from the validated parsed pathname, so trailing slashes never produce double slashes or a missing path segment.

Provider request deadlines

The protocol adapters share one deadline helper (providers/timeout.ts). requestTimeoutMs bounds the whole HTTP request as total elapsed time, not each idle interval. Omitted or null means unlimited. Progress never resets the deadline: request setup, error-body reads, and every response-body pull are bounded by the time remaining since the request started. Expiration fails the attempt with ModelTimeoutFailure (timeoutKind: 'attempt-limit') after the adapter's normal body cleanup aborts the host request.

Wire parser bounds

The protocol adapters share one size bound (providers/limits.ts): a single SSE event and a single HTTP error body are each capped at 1 MiB. Overflow fails the attempt with a typed malformed failure, so one pathological payload cannot exhaust host memory before a typed failure is produced.

Credential boundary threat model

The provider already holds the API key by design (it authenticates every request). The boundary keeps the key out of the opaque values a provider hands back, which a proxy or gateway that reflects the Authorization header could echo: response IDs and their continuation reference, the provider finish reason, and failure evidence (provider codes, request IDs, header evidence). A value that contains the credential is dropped, not rewritten.

Model output (text, reasoning, tool input, and continuation payloads) is not rewritten. As in OpenCode and Pi, keep secrets out of what the model can read instead: route calls through a gateway that holds the provider key, and carry credentials as Redacted values to the request header.

Non-goals: the boundary is not a sandbox (adapters are trusted code), and it does not defend against a provider that misuses the key it already holds.

Images in tool results

ToolResultValue accepts { type: 'content', content: [...] } alongside text and JSON. Each content part is text, JSON, or an image using the same URL/base64 references as user images. The durable equivalent is CanonicalToolResult with format: 'content' and the parts in value.

Responses and the Codex Responses deployment send these parts inside function_call_output.output. Anthropic Messages nests them in tool_result.content. Chat Completions accepts only text in tool replies, so its adapter sends text and JSON in the tool reply and adds labeled images in a user message after the contiguous tool replies. Images retain their tool call IDs in those labels; canonical history keeps them attached to the original tool result.

Public TypeScript exports

  • AssistantContentPart
  • AssistantMessage
  • builtinCredentialTransports
  • builtinLoginModule
  • canonicalizeProviderFailure
  • CompatibleModelDiscovery
  • CompatibleProviderAuthentication
  • CompatibleProviderConfig
  • CompatibleProviderConfigValue
  • CompatibleProviderCredentials
  • CompatibleProviderCredentialsValue
  • CompatibleProviderFailureRules
  • CompatibleProviderOptions
  • CompatibleProviderOptionsValue
  • CompatibleProviderProtocolAuthentication
  • CompatibleProviderProtocolHeaders
  • compatibleProviderRequiresCredential
  • CompatibleProviderRoutes
  • CompletedContentPart
  • CredentialTransport
  • CredentialTransportBinding
  • credentialTransportRegistry
  • CredentialTransportRegistry
  • defineProvider
  • DeviceLoginDescriptor
  • discoverCompatibleModels
  • discoverDeviceLogin
  • DiscoveredRemoteModel
  • FinishReason
  • GenerationSettings
  • InvalidProviderDefinitionError
  • joinProviderEndpoint
  • LegacyProviderLogin
  • makeCodexLogin
  • makeCompatibleProviderDefinition
  • makeModelLayer
  • makeModelTurnContext
  • makeOAuth2DeviceLogin
  • makeScriptedModelLayer
  • MalformedProviderDataFailure
  • MalformedProviderResponseFailure
  • MalformedProviderStreamFailure
  • Model
  • ModelAttemptUsage
  • ModelAuthenticationFailure
  • ModelCallIdentity
  • ModelContextOverflowFailure
  • ModelEvent
  • ModelFailure
  • ModelImplementation
  • ModelIncompleteResponseFailure
  • ModelInstruction
  • ModelInstructions
  • ModelInvalidRequestFailure
  • ModelMessage
  • ModelMessages
  • ModelPermissionFailure
  • ModelProtocol
  • modelProviderId
  • ModelRateLimitFailure
  • ModelRequest
  • ModelRequestChain
  • ModelRequestChainImplementation
  • ModelRequestState
  • ModelRetryDirective
  • ModelService
  • ModelStreamTerminationFailure
  • ModelTimeoutFailure
  • ModelToolCallPart
  • ModelToolChoice
  • ModelToolDescriptor
  • ModelTransportFailure
  • ModelTurnContext
  • modelTurnContextCall
  • modelTurnContextSessionId
  • ModelUnavailableFailure
  • ModelUnsupportedCapabilityFailure
  • NoProviderOptions
  • normalizeProviderFailure
  • OAuth2RefreshFailure
  • openModelRequestChain
  • PrivateContinuationArtifact
  • PrivateContinuationArtifacts
  • PrivateMessageContinuation
  • PrivateMessageContinuations
  • ProviderBilledUnit
  • ProviderCost
  • ProviderCredentialSource
  • ProviderDefinition
  • ProviderFailureInput
  • ProviderLoginAttempt
  • ProviderLoginError
  • ProviderLoginMethodImplementation
  • ProviderLoginModule
  • ProviderModelAcquisitionInput
  • ProviderModelCorrection
  • ProviderModelDiscoveryError
  • ProviderModelFilter
  • ProviderRequestOptions
  • ProviderResponseId
  • ProviderUsageProvenance
  • ReasoningBoundaryPart
  • ReasoningPart
  • refreshOAuth2Credential
  • RefusalPart
  • RunnerCredential
  • ScriptedModel
  • ScriptedModelControl
  • ScriptedModelRequest
  • ScriptedModelScript
  • ScriptedModelStep
  • staticRunnerCredential
  • TextPart
  • ToolJsonSchema
  • ToolMessage
  • ToolResultPart
  • ToolResultValue
  • upgradeLegacyCodexCredential
  • UserContentPart
  • UserMessage
  • validateModelEventStream