SharedOS API v1.0.0-preview


SharedOS API / @aicoo/sharedos-adapters

@aicoo/sharedos-adapters

What sits in the SharedOS delegate seat: a model API behind the standard driver, or a vendor CLI (Codex, Claude Code, DeepSeek Harness, Pi) connected over MCP.

An adapter is translation and nothing else. The permission-filtered tool catalogue, per-call re-authorization, the turn's limits and audit all come from the SharedOS execution envelope, so seating another model or another harness changes no kernel code and adds no second permission path.

A model in the seat

import {
  SharedOSExecutor,
  createStandardRuntime,
} from "@aicoo/sharedos-runtime";
import {
  OpenAiCompatibleModelClient,
  StandardTurnDriver,
} from "@aicoo/sharedos-adapters";

const runtime = createStandardRuntime({
  driver: new StandardTurnDriver({
    manifest: { id: "acme.assistant", version: "1.0.0", protocolVersion: "1" },
    client: new OpenAiCompatibleModelClient({ baseUrl, apiKey, model }),
  }),
});
const turns = new SharedOSExecutor(kernel, runtime);

createStandardRuntime is the SharedOS loop; StandardTurnDriver is the SharedOS driver for it. The driver renders the catalogue into the model's tool-call shape, reads each reply back into a decision, and recognises the escalate affordance only when the turn's catalogue offers it. The runtime reports the seated driver's manifest, so every record names what sat in the seat. A host with a model path of its own seats its own AgentTurnDriver in the same slot.

A vendor CLI in the seat

import {
  CLAUDE_CODE_MCP_HARNESS,
  createMcpHarnessRuntime,
} from "@aicoo/sharedos-adapters/node";

const turns = new SharedOSExecutor(
  kernel,
  createMcpHarnessRuntime(CLAUDE_CODE_MCP_HARNESS),
);

The CLI keeps its own loop and its own model, and the turn's catalogue is served to it over the Model Context Protocol for the length of the turn. It is documented in docs/mcp-toolshare.md. A turn served this way is bounded by maxToolCalls and timeoutMs; a harness declares no step.

What can occupy the seat

PathWhat is in the delegate seatEntry points
ModelA model API, with no vendor between it and the kernelStandardTurnDriver in createStandardRuntime; OpenAiCompatibleModelClient, or TranscriptModelClient for scripted replies
Vendor CLI over MCPA vendor CLI running its own loop, with the catalogue served to itcreateMcpHarnessRuntime and the *_MCP_HARNESS specs, from @aicoo/sharedos-adapters/node
Host's own driverWhatever the host's AgentTurnDriver speaks tocreateStandardRuntime({ driver })

Both converge on RuntimeHost.invokeTool, which is the only place a tool is executed.

Evaluation only: EvalHarnessDriver seats a vendor's exact wire format in the standard loop, over a HarnessTransport (TranscriptTransport for recorded frames, ChildProcessTransport for a live CLI's stdio). The conformance columns use it to grade a vendor's codec against the kernel, and it is the one driver that can declare a step past its budget. It is not a way to run a vendor CLI in a product: no coding-agent CLI accepts a host-supplied catalogue on its own protocol, which is what the MCP path is for. Each vendor's codec, manifests and requirements are stated once as its *_VENDOR descriptor.

What the delegate is told

The catalogue says which tools exist; RuntimeVisibleContext.reach says where they are worth pointing. The execution envelope computes it once per turn, from the grants every decision in that turn is made against, narrowed to the namespaces the offered tools operate on. The adapters are where it reaches a model:

PathWhere the reach goes
ModelA system message ahead of the prompt
Vendor CLI over MCPThe server's initialize instructions, which a harness that honours them puts where its model reads
Evaluation driverHarnessTurnRequest.instructions, which the transport hands over; harnessTurnText is the two as one text

Every path takes the same instructions option (SeatTextOptions): a string is the host's standing guidance, placed before the turn's reach; a function says exactly what the seat is told, and undefined from it hands over nothing. What was handed over is hashed with the prompt as the turn's promptHash.

describeReach, from @aicoo/sharedos-runtime, says the same thing on every path: each entry as its namespace, its path as the JSON array a path argument takes, and whether it covers what lies beneath; an empty reach as "nowhere"; an unavailable reach as exactly that, with its reason code, never as an empty list. Every rendering says it is descriptive. The kernel decides each call the model goes on to make, so an entry is not a permission and a missing one is not a refusal.

What a seat states about its turn

Every seat here writes its facts about a turn onto ExecutionResult.metadata under one vocabulary, the exported SeatMetadata type, so the same fact is under the same key whichever seat ran. A seat states the keys that apply to it and leaves the rest absent.

KeyStated byWhat it says
model, modelProviderbothThe standard driver states the model the provider served; the MCP harness runtime states the one the run declared, because a vendor CLI selects its own
requestedModel, modelSettingsstandard driverWhat was asked for, when the served model may differ
finishReason, inputTokens, outputTokensstandard driverWhy the last reply ended, and the turn's summed spend; absent, never zero, when the provider reports none
malformedToolCallsstandard driverCalls refused in place for unreadable arguments; none reached the envelope
harness, toolshare, mcpServerMCP harness runtimeWhich vendor CLI ran and how it reached the catalogue
catalogHash, toolAliasesMCP harness runtimeThe catalogue the harness was served, and the names it rewrote (diagnostic only)
harnessOutcome, harnessErrorCodeMCP harness runtimeOn an escalated turn, how the CLI itself ended
callsAfterEscalationMCP harness runtimeOn an escalated turn, calls the CLI made after its ask; each was answered escalation_pending and reached no kernel

The conformance record lifts model, modelProvider, catalogHash, the token counts and callsAfterEscalation; the rest are for the host that ran the turn. The envelope adds its own keys beside these: runtime, promptHash and escalationAsked.

The three pieces of the evaluation driver

A vendor's codec is graded from parts that are replaceable independently, which is what lets the translation be verified without the vendor's CLI present.

PieceResponsibility
HarnessProtocolThe vendor's wire shapes: tool declarations, tool calls, tool results, completion
HarnessTransportHow the harness is reached: a subprocess, an HTTP session, a supplied transcript
EvalHarnessDriverAn AgentTurnDriver that joins the two and hands every tool call to the envelope

StandardTurnDriver is the same shape with the protocol folded in: the catalogue is rendered straight into the model's tool-call format, and a ModelClient stands where the transport does.

What the adapter must not do

Tool calls are passed to the envelope as the harness emitted them, including names that are not in the catalogue.

Filtering them in the adapter would be the adapter quietly enforcing policy, and worse, it would erase the attempt: a guess at an unexposed tool has to reach the envelope to be refused and recorded. An adapter that silently dropped it would make a harness that tried look identical to one that did not.

The one call that does not reach the envelope is the escalation affordance. A call naming sharedos.escalate on a turn whose catalogue offers it ends the turn escalated with the reason the harness gave, because asking for a human is an ending rather than a tool (ADR 0011, 0017). The catalogue gates the name: on a turn that was never granted the affordance the call is passed through like any other and refused tool_unavailable.

For the same reason a refusal is reported back to the harness as an ordinary tool result carrying its reason code, not as a transport error. The harness needs to know it was refused so it can choose differently. A model whose call arguments are not a JSON object is answered the same way, invalid_tool_arguments, and never sent {} in their place.

Tool calls arriving together in one frame are executed one at a time. SharedOS re-authorizes every call separately, so serialising them is the conservative order and the one whose audit trail matches what actually happened.

Who executes the tools

The four harnesses do not agree on this, and the difference decides what a driven column can claim.

HarnessCatalogue reaches the harness byTool executed by
Codexfunction declarations, on the wireThe host
Claude Codeinput_schema tools, on the wireThe host
DeepSeekOut of band — its dsh-mcp-client plugin, over MCPThe host, via MCP
PiOut of band — an MCP extension, or defineTool in the SDKThe host, via MCP

Codex and Claude Code carry a tool catalogue in the protocol itself. DeepSeek Harness and Pi run their own tools and have no wire frame that means "here is your catalogue", so their driven adapters stamp catalogueDelivery: "out-of-band" onto every execution record they produce: a column whose catalogue arrived out of band is making a narrower claim than one whose catalogue was on the wire, and that belongs in the evidence rather than in a footnote.

It is also why a native run over a CLI's own stdio — any of the four — leaves the kernel rows not exercised: the driver can carry the transport, but a CLI exposes neither its API layer nor a frame for the catalogue (ADR 0014), so the harness reaches for its own tools. The MCP path is what closes that gap. createMcpHarnessRuntime serves the permission-filtered catalogue over MCP to a CLI running natively; scripts/mcp-conformance.mjs runs the case set against each installed CLI that way, and ADR 0018 records what the escalation-case runs on all four showed.

Verification status

The translation code is exercised end to end against supplied transcripts, which run the real protocol modules through a real kernel and a real execution envelope. Nothing in this package captures a vendor session: a transcript is whatever its caller hands it, and the conformance suite writes its own. TranscriptTransport replays vendor frames in batches and releases the next batch only once a result has been written, which is the shape of every tool-using harness. TranscriptModelClient is its counterpart for the model seat: it replays supplied replies through the real StandardTurnDriver, one reply per model call, and treats a spent transcript as an error rather than a completion, so a script that ends too early fails the turn instead of reading as a model choosing to stop.

What a transcript cannot cover is the transport binding — the exact command-line flags each CLI wants, and the outer envelope it wraps its frames in — and what a model actually chooses. Two scripts cover exactly those gaps:

  • scripts/native-conformance.mjs spawns each installed CLI as a driven harness, and runs the model column when a key is present;
  • scripts/mcp-conformance.mjs runs each installed CLI natively against the catalogue over MCP.

Both report a harness that is absent, unauthenticated, or emitting shapes the adapter does not parse as not exercised, never as a pass and never as a kernel failure. The version each run drove is the harness's own to report, so it is recorded in the artifact the script writes under artifacts/conformance/ — local to the machine that ran it, not committed — rather than pinned here; ADR 0014 and ADR 0018 pin the versions of the runs they record.

Availability

probeHarness reports whether a harness can run here, and says why not when it cannot. probeCodex, probeClaudeCode, probeDeepseek, and probePi are the same call with each adapter's *_REQUIREMENTS supplied:

import { probeClaudeCode } from "@aicoo/sharedos-adapters/node";

const availability = await probeClaudeCode();
// { harness: "claude-code", available: false, reason: "The claude executable is not on PATH." }
// or { harness: "claude-code", available: true, version: "…" }

Every one of these harnesses can authenticate from a stored login as well as from an environment variable, so a probe treats credentials as optional unless the requirements say otherwise, and reports which one it found. Conformance runs use this to mark a column as not exercised rather than as failing: an absent harness is not evidence about SharedOS.

Reason codes

The codes an adapter ends a turn with — harness_*, model_* — and the one it answers in band on the MCP path, escalation_pending, are listed with the rest in docs/errors.md.

Host neutrality

The main entry point has no Node dependency. ChildProcessTransport, the availability probes, and the MCP harness runtime are published from @aicoo/sharedos-adapters/node, because spawning a CLI, reading PATH, and opening a loopback server are host concerns rather than protocol ones.

Classes

EvalHarnessDriver

Defined in: packages/adapters/src/driver.ts:59

One vendor's wire format, seated in the standard loop for evaluation.

It puts SharedOS in the model provider's seat and speaks the vendor's API-layer tool-call shape exactly, which is how a vendor's codec is graded against the kernel: over a recorded transcript in the committed conformance columns, or over a live CLI's stdio in scripts/native-conformance.mjs. It is not how a vendor CLI runs in a product. No coding-agent CLI accepts a host-supplied catalogue on its own protocol; createMcpHarnessRuntime is the path for that. It is also the one driver that can name a step past its budget (declareStep), which only an evaluation has a reason to do.

A driver is only translation. The turn loop, the permission-filtered catalogue, per-call re-authorization, and audit all belong to the SharedOS execution envelope, which is why adding a harness requires no kernel change and no second enforcement path.

Tool calls are passed through exactly as the harness emitted them, including names that are not in the catalogue. Filtering those here would be the adapter quietly enforcing policy, and worse, it would erase the attempt: a guess at an unexposed tool has to reach the envelope to be refused and recorded.

Implements

Constructors

Constructor

new EvalHarnessDriver(options): EvalHarnessDriver

Defined in: packages/adapters/src/driver.ts:67

Parameters
ParameterType
optionsEvalHarnessDriverOptions
Returns

EvalHarnessDriver

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-manifest"></a> manifestreadonlyobjectWho this driver is, for the record. The executor stamps the plugin's manifest on every execution record, and the loop is the same whichever driver is seated, so the loop reports the seated driver's manifest as its own: evidence is filed under what produced it. A driver that states none is reported as sharedos.standard.packages/adapters/src/driver.ts:60
manifest.idpublicstring-packages/contracts/dist/runtime.d.ts:9
manifest.metadata?publicJsonObject-packages/contracts/dist/runtime.d.ts:12
manifest.protocolVersionpublic"1"-packages/contracts/dist/runtime.d.ts:11
manifest.versionpublicstring-packages/contracts/dist/runtime.d.ts:10

Methods

open()

open(request, signal): Promise<AgentTurnSession>>

Defined in: packages/adapters/src/driver.ts:82

Open the session one turn is driven through.

A turn cancelled while this is in flight stops waiting for it. A session handed back after that is still closed, with the turn's ending, so close may be called on a session that was never asked for a decision. A driver whose open rejects releases whatever it had taken itself: there is no session to close.

Parameters
ParameterType
requestRuntimeTurnRequest
signalAbortSignal
Returns

Promise<AgentTurnSession>

Implementation of

AgentTurnDriver.open


ModelRequestError

Defined in: packages/adapters/src/model/client.ts:107

A model call that did not produce an answer. Carries no response body.

Extends

  • Error

Constructors

Constructor

new ModelRequestError(message, status?): ModelRequestError

Defined in: packages/adapters/src/model/client.ts:110

Parameters
ParameterType
messagestring
status?number
Returns

ModelRequestError

Overrides

Error.constructor

Properties

PropertyModifierTypeDescriptionInherited fromDefined in
<a id="property-cause"></a> cause?publicunknown-Error.causenode_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.error.d.ts:26
<a id="property-message"></a> messagepublicstring-Error.messagenode_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts:1077
<a id="property-name"></a> namepublicstring-Error.namenode_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts:1076
<a id="property-stack"></a> stack?publicstring-Error.stacknode_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts:1078
<a id="property-status"></a> status?readonlynumber--packages/adapters/src/model/client.ts:108
<a id="property-stacktracelimit"></a> stackTraceLimitstaticnumberThe Error.stackTraceLimit property specifies the number of stack frames collected by a stack trace (whether generated by new Error().stack or Error.captureStackTrace(obj)). The default value is 10 but may be set to any valid JavaScript number. Changes will affect any stack trace captured after the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames.Error.stackTraceLimitnode_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/globals.d.ts:68

Methods

captureStackTrace()

static captureStackTrace(targetObject, constructorOpt?): void

Defined in: node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/globals.d.ts:52

Creates a .stack property on targetObject, which when accessed returns a string representing the location in the code at which Error.captureStackTrace() was called.

const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`

The first line of the trace will be prefixed with ${myObject.name}: ${myObject.message}.

The optional constructorOpt argument accepts a function. If given, all frames above constructorOpt, including constructorOpt, will be omitted from the generated stack trace.

The constructorOpt argument is useful for hiding implementation details of error generation from the user. For instance:

function a() {
  b();
}

function b() {
  c();
}

function c() {
  // Create an error without stack trace to avoid calculating the stack trace twice.
  const { stackTraceLimit } = Error;
  Error.stackTraceLimit = 0;
  const error = new Error();
  Error.stackTraceLimit = stackTraceLimit;

  // Capture the stack trace above function b
  Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
  throw error;
}

a();
Parameters
ParameterType
targetObjectobject
constructorOpt?Function
Returns

void

Inherited from

Error.captureStackTrace

prepareStackTrace()

static prepareStackTrace(err, stackTraces): any

Defined in: node_modules/.pnpm/@types+node@22.20.1/node_modules/@types/node/globals.d.ts:56

Parameters
ParameterType
errError
stackTracesCallSite[]
Returns

any

See

https://v8.dev/docs/stack-trace-api#customizing-stack-traces

Inherited from

Error.prepareStackTrace


OpenAiCompatibleModelClient

Defined in: packages/adapters/src/model/client.ts:213

A chat-completions client for any provider speaking the OpenAI wire shape.

DeepSeek is the one this was built against, but nothing here is DeepSeek specific: the endpoint, model, and provider label are all supplied, so pointing the column at another compatible provider is configuration rather than a second client.

Implements

Constructors

Constructor

new OpenAiCompatibleModelClient(options): OpenAiCompatibleModelClient

Defined in: packages/adapters/src/model/client.ts:225

Parameters
ParameterType
optionsOpenAiCompatibleModelClientOptions
Returns

OpenAiCompatibleModelClient

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-model"></a> modelreadonlystringThe model this client was configured to ask for.packages/adapters/src/model/client.ts:214
<a id="property-provider"></a> providerreadonlystringThe provider that serves it, recorded alongside the model on every turn.packages/adapters/src/model/client.ts:215
<a id="property-settings"></a> settings?readonlyJsonObjectSettings this client sends that change what the model does, beyond naming it -- a reasoning mode, say. Recorded on every turn beside the model so two runs under one model name are not read as the same configuration when they were not. Absent when the client sends nothing the provider would not default to.packages/adapters/src/model/client.ts:216

Methods

complete()

complete(request, signal): Promise<ModelReply>>

Defined in: packages/adapters/src/model/client.ts:243

Parameters
ParameterType
requestModelCompletionRequest
signalAbortSignal
Returns

Promise<ModelReply>

Implementation of

ModelClient.complete


StandardTurnDriver

Defined in: packages/adapters/src/model/driver.ts:135

The SharedOS driver for the standard loop: a model API in the seat.

"Standard" names the SharedOS-owned default at each layer, and this is the default driver: what createStandardRuntime seats when a host has no model path of its own. The evaluation driver translates frames from a vendor's wire format; this one puts the model itself in the seat, so the catalogue it sees is the permission-filtered one the kernel built and nothing between the two can add a tool, drop a tool, or answer a call on its own.

What that buys is an axis the other columns cannot separate. A scripted column leaves out the transport; a live CLI column leaves out the catalogue; an MCP column keeps both but hands the turn loop to the vendor's scaffolding. This one keeps the loop with SharedOS and drops the vendor entirely, which is what makes "the model behaved this way" distinguishable from "the vendor's scaffolding made the model behave this way".

It is not a replacement for the scripted adversary and cannot be one. A model chooses what to call, so an attempt it declines to issue leaves no operation in the record and is graded as unexercised. That is the honest grading, and the reason the deterministic column stays the reference.

Implements

Constructors

Constructor

new StandardTurnDriver(options): StandardTurnDriver

Defined in: packages/adapters/src/model/driver.ts:142

Parameters
ParameterType
optionsStandardTurnDriverOptions
Returns

StandardTurnDriver

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-manifest-1"></a> manifestreadonlyobjectWho this driver is, for the record. The executor stamps the plugin's manifest on every execution record, and the loop is the same whichever driver is seated, so the loop reports the seated driver's manifest as its own: evidence is filed under what produced it. A driver that states none is reported as sharedos.standard.packages/adapters/src/model/driver.ts:136
manifest.idpublicstring-packages/contracts/dist/runtime.d.ts:9
manifest.metadata?publicJsonObject-packages/contracts/dist/runtime.d.ts:12
manifest.protocolVersionpublic"1"-packages/contracts/dist/runtime.d.ts:11
manifest.versionpublicstring-packages/contracts/dist/runtime.d.ts:10

Methods

open()

open(request, _signal): Promise<AgentTurnSession>>

Defined in: packages/adapters/src/model/driver.ts:156

Open the session one turn is driven through.

A turn cancelled while this is in flight stops waiting for it. A session handed back after that is still closed, with the turn's ending, so close may be called on a session that was never asked for a decision. A driver whose open rejects releases whatever it had taken itself: there is no session to close.

Parameters
ParameterType
requestRuntimeTurnRequest
_signalAbortSignal
Returns

Promise<AgentTurnSession>

Implementation of

AgentTurnDriver.open


ToolNameCodec

Defined in: packages/adapters/src/model/driver.ts:54

How a SharedOS tool name is spoken to a model, and read back.

Dots become underscores on the way out and the catalogue's own map decides the way back, so a catalogued tool round-trips exactly rather than through a guess. The map is built per turn from the permission-filtered catalogue, which means it contains precisely the tools this actor was allowed to see.

A name the map does not contain is decoded by reversing the substitution and then passed through unchanged. That path is best-effort and it exists for one reason: a model that invents a tool outside its catalogue must still be able to reach the envelope and be refused. Filtering it here, or failing to decode it, would erase the attempt -- and an attempt that never arrives is graded as a tool that was never tried, not as a tool that was refused.

Constructors

Constructor

new ToolNameCodec(tools): ToolNameCodec

Defined in: packages/adapters/src/model/driver.ts:58

Parameters
ParameterType
toolsreadonly object[]
Returns

ToolNameCodec

Methods

fromWire()

fromWire(name): string

Defined in: packages/adapters/src/model/driver.ts:84

Parameters
ParameterType
namestring
Returns

string

toWire()

toWire(name): string

Defined in: packages/adapters/src/model/driver.ts:80

Parameters
ParameterType
namestring
Returns

string


TranscriptModelClient

Defined in: packages/adapters/src/model/transcript.ts:38

Replays a supplied conversation through the real model driver.

This is how the native harness is verified without a provider or a credential present, and it is the exact counterpart of TranscriptTransport for a vendor harness. The replies are the caller's, written in the model's own tool-call shape; the name decoding, argument parsing, escalation recognition, and step accounting are the driver's; and the only thing left unexercised is the provider that would have produced the replies.

A spent transcript is an error rather than a completion. A live provider always answers; a recording that has run out has nothing to say, and answering "done" on its behalf would grade a script that ended too early as a model choosing to stop. The driver fails the turn model_call_failed, which is the visible result.

Implements

Constructors

Constructor

new TranscriptModelClient(transcript, options?): TranscriptModelClient

Defined in: packages/adapters/src/model/transcript.ts:46

Parameters
ParameterType
transcriptModelTranscript
optionsTranscriptModelClientOptions
Returns

TranscriptModelClient

Properties

PropertyModifierTypeDefault valueDescriptionDefined in
<a id="property-model-1"></a> modelreadonlystringundefinedThe model this client was configured to ask for.packages/adapters/src/model/transcript.ts:39
<a id="property-provider-1"></a> providerreadonlystringundefinedThe provider that serves it, recorded alongside the model on every turn.packages/adapters/src/model/transcript.ts:40
<a id="property-seen"></a> seenreadonlyModelCompletionRequest[][]Every request the driver made, in order, for a test to read back.packages/adapters/src/model/transcript.ts:42

Methods

complete()

complete(request, signal): Promise<ModelReply>>

Defined in: packages/adapters/src/model/transcript.ts:55

Parameters
ParameterType
requestModelCompletionRequest
signalAbortSignal
Returns

Promise<ModelReply>

Implementation of

ModelClient.complete


TranscriptTransport

Defined in: packages/adapters/src/transcript.ts:27

Replays a supplied conversation through the real protocol translation.

This is how an adapter is verified without the vendor's CLI or credentials present. The frames are the vendor's, the parsing is the adapter's, and the only thing left unexercised is the transport that would have carried them.

Implements

Constructors

Constructor

new TranscriptTransport(transcript): TranscriptTransport

Defined in: packages/adapters/src/transcript.ts:32

Parameters
ParameterType
transcriptHarnessTranscript
Returns

TranscriptTransport

Properties

PropertyModifierTypeDefault valueDefined in
<a id="property-opened"></a> openedreadonlyHarnessTurnRequest[][]packages/adapters/src/transcript.ts:28
<a id="property-written"></a> writtenreadonlyJsonObject[][]packages/adapters/src/transcript.ts:29

Methods

open()

open(request): Promise<HarnessChannel>>

Defined in: packages/adapters/src/transcript.ts:39

Parameters
ParameterType
requestHarnessTurnRequest
Returns

Promise<HarnessChannel>

Implementation of

HarnessTransport.open

Interfaces

EvalHarnessDriverOptions

Defined in: packages/adapters/src/driver.ts:24

instructions reaches the harness on HarnessTurnRequest.instructions, for its transport to hand over with the prompt.

Extends

Properties

PropertyModifierTypeDescriptionInherited fromDefined in
<a id="property-declarestep"></a> declareStep?readonlyDeclareStepSee DeclareStep.-packages/adapters/src/driver.ts:31
<a id="property-instructions"></a> instructions?readonlystring | ((request) => string | undefined)What the seat is told before the prompt: a model's system message, an MCP server's initialize instructions, a harness's preamble. By default it is request.context.reach rendered by describeReach: where this turn's tools may operate, with the authority left out. The prompt carries the task and this carries the environment the task runs in. A string is the host's standing guidance, placed before the turn's reach so a seat that shows its model one block reads the guidance before the map. A function replaces the composition and says exactly what the seat is told; returning undefined hands over no instructions at all.SeatTextOptions.instructionspackages/adapters/src/seat.ts:40
<a id="property-manifest-2"></a> manifestreadonlyobject--packages/adapters/src/driver.ts:25
manifest.idpublicstring--packages/contracts/dist/runtime.d.ts:9
manifest.metadata?publicJsonObject--packages/contracts/dist/runtime.d.ts:12
manifest.protocolVersionpublic"1"--packages/contracts/dist/runtime.d.ts:11
manifest.versionpublicstring--packages/contracts/dist/runtime.d.ts:10
<a id="property-maxignoredframes"></a> maxIgnoredFrames?readonlynumberGuard against a harness that streams unrelated frames without end.-packages/adapters/src/driver.ts:29
<a id="property-prompt"></a> prompt?readonly(request) => stringOverrides how the turn message becomes the prompt.SeatTextOptions.promptpackages/adapters/src/seat.ts:27
<a id="property-protocol"></a> protocolreadonlyHarnessProtocol--packages/adapters/src/driver.ts:26
<a id="property-transport"></a> transportreadonlyHarnessTransport--packages/adapters/src/driver.ts:27

HarnessAvailability

Defined in: packages/adapters/src/harness.ts:100

Whether a harness can actually be run here, and if not, why not.

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-available"></a> availablereadonlyboolean-packages/adapters/src/harness.ts:102
<a id="property-detail"></a> detail?readonlyJsonObjectIncludes versionOutput, the line version was read from, verbatim.packages/adapters/src/harness.ts:115
<a id="property-harness"></a> harnessreadonlystring-packages/adapters/src/harness.ts:101
<a id="property-reason"></a> reason?readonlystring-packages/adapters/src/harness.ts:103
<a id="property-version"></a> version?readonlystringThe build that answered, as the harness itself reports it. A result about a vendor CLI is a result about one version of it, and the version is the harness's to state: nothing in this repository pins the installed binary, and a number carried in a runbook is a claim about what someone typed rather than about what ran. Absent when the executable declined to report one -- see HarnessRequirements.versionArguments.packages/adapters/src/harness.ts:113

HarnessChannel

Defined in: packages/adapters/src/harness.ts:62

One open harness turn. Reads and writes are frames, never SharedOS types.

Methods

close()

close(): Promise<void>>

Defined in: packages/adapters/src/harness.ts:66

Returns

Promise<void>

read()

read(signal): Promise<JsonObject | undefined>>

Defined in: packages/adapters/src/harness.ts:64

The next frame, or undefined once the harness has finished speaking.

Parameters
ParameterType
signalAbortSignal
Returns

Promise<JsonObject | undefined>

write()

write(frame, signal): Promise<void>>

Defined in: packages/adapters/src/harness.ts:65

Parameters
ParameterType
frameJsonObject
signalAbortSignal
Returns

Promise<void>


HarnessFrameWriter

Defined in: packages/adapters/src/writer.ts:22

The inverse of HarnessProtocol.interpret: frames a harness would send.

A HarnessProtocol only ever reads. That is correct for production, where the frames come from the vendor, and it leaves no way to build a recorded conversation for a harness to be replayed against. Writing those frames by hand per test is how a fixture drifts from the shape the parser expects, so the two live side by side and are exercised against each other.

A writer is deliberately not part of HarnessProtocol. Requiring every adapter to implement an encoder that production never calls would put dead code in the security-relevant path.

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-protocolid"></a> protocolIdreadonlystringThe protocol these frames belong to; must match the reading protocol's id.packages/adapters/src/writer.ts:24

Methods

complete()

complete(output?): JsonObject

Defined in: packages/adapters/src/writer.ts:27

Parameters
ParameterType
output?JsonValue
Returns

JsonObject

message()

message(text): JsonObject

Defined in: packages/adapters/src/writer.ts:26

Parameters
ParameterType
textstring
Returns

JsonObject

toolCall()

toolCall(callId, tool, arguments_): JsonObject

Defined in: packages/adapters/src/writer.ts:25

Parameters
ParameterType
callIdstring
toolstring
arguments_JsonObject
Returns

JsonObject


HarnessProtocol

Defined in: packages/adapters/src/harness.ts:86

The translation between SharedOS and one vendor's wire shapes.

This is the whole adapter. Everything else -- the turn loop, per-call re-authorization, the permission-filtered catalogue, audit -- is supplied by the SharedOS execution envelope and is not reimplemented per vendor.

Properties

PropertyModifierTypeDefined in
<a id="property-id"></a> idreadonlystringpackages/adapters/src/harness.ts:87

Methods

describeTools()

describeTools(tools): JsonValue

Defined in: packages/adapters/src/harness.ts:89

Render the permission-filtered catalogue in the harness's own tool shape.

Parameters
ParameterType
toolsreadonly object[]
Returns

JsonValue

encodeToolResult()

encodeToolResult(result): JsonObject

Defined in: packages/adapters/src/harness.ts:96

Parameters
ParameterType
result{ callId: string; completedAt: string; metadata?: JsonObject; output: JsonValue; status: "succeeded"; tool: string; } | { callId: string; completedAt: string; error: { code: string; details?: JsonObject; message: string; retryable?: boolean; }; metadata?: JsonObject; status: "denied"; tool: string; } | { callId: string; completedAt: string; error: { code: string; details?: JsonObject; message: string; retryable?: boolean; }; metadata?: JsonObject; status: "failed"; tool: string; }
Returns

JsonObject

interpret()

interpret(frame): readonly HarnessStep[]

Defined in: packages/adapters/src/harness.ts:95

Everything one frame means, in order. Frames carrying nothing relevant -- progress notices, token counts, thinking blocks -- yield an empty array, and a frame carrying several tool calls yields one step each.

Parameters
ParameterType
frameJsonObject
Returns

readonly HarnessStep[]


HarnessRequirements

Defined in: packages/adapters/src/harness.ts:119

What a harness needs before it can run: an executable, credentials, or both.

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-credentialsoptional"></a> credentialsOptionalreadonlybooleanTrue when the harness can authenticate from a stored session instead.packages/adapters/src/harness.ts:126
<a id="property-credentialvariables"></a> credentialVariablesreadonlyreadonly string[]Environment variables, any one of which satisfies the credential need.packages/adapters/src/harness.ts:124
<a id="property-executable"></a> executablereadonlystringExecutable expected on PATH.packages/adapters/src/harness.ts:122
<a id="property-harness-1"></a> harnessreadonlystring-packages/adapters/src/harness.ts:120
<a id="property-versionarguments"></a> versionArguments?readonlyreadonly string[]How to ask this executable what it is. Defaults to --version, which all four harnesses here answer; declared so one that does not can say so.packages/adapters/src/harness.ts:131

HarnessTranscript

Defined in: packages/adapters/src/transcript.ts:16

A harness conversation, supplied by its caller.

Batches are released one tool result at a time: the first batch is emitted when the turn opens, and each later batch is unlocked by the adapter writing a result back. That is the shape of every tool-using harness, so a transcript exercises the same code path a live session does.

Properties

PropertyModifierTypeDefined in
<a id="property-batches"></a> batchesreadonlyreadonly readonly JsonObject[][]packages/adapters/src/transcript.ts:17

HarnessTransport

Defined in: packages/adapters/src/harness.ts:75

How a harness is reached: a subprocess, an HTTP session, or a recorded transcript. Keeping this separate from the protocol is what lets one adapter be exercised deterministically and then run live without changing the translation code under test.

Methods

open()

open(request, signal): Promise<HarnessChannel>>

Defined in: packages/adapters/src/harness.ts:76

Parameters
ParameterType
requestHarnessTurnRequest
signalAbortSignal
Returns

Promise<HarnessChannel>


HarnessTurnRequest

Defined in: packages/adapters/src/harness.ts:31

Everything a harness needs to start one turn.

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-context"></a> contextreadonlyRuntimeVisibleContextThe sanitised context. It carries no grants and no issuing authority.packages/adapters/src/harness.ts:44
<a id="property-executionid"></a> executionIdreadonlystring-packages/adapters/src/harness.ts:32
<a id="property-instructions-1"></a> instructions?readonlystringWhat the harness is told before the prompt: by default where this turn's tools may operate. It is part of the turn's prompt hash, so a transport that opens the harness hands it over; harnessTurnText is the text for a harness whose opening frame has one slot.packages/adapters/src/harness.ts:40
<a id="property-metadata"></a> metadata?readonlyJsonObject-packages/adapters/src/harness.ts:45
<a id="property-prompt-1"></a> promptreadonlystring-packages/adapters/src/harness.ts:33
<a id="property-tools"></a> toolsreadonlyJsonValueThe permission-filtered catalogue, already in the harness's own shape.packages/adapters/src/harness.ts:42

HarnessVendor

Defined in: packages/adapters/src/vendors.ts:16

What one vendor harness is, stated once: its id, its codec, what it needs, how its records are named.

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-id-1"></a> idreadonly"codex" | "claude-code" | "deepseek" | "pi"The id this harness goes by everywhere: manifests, requirements, MCP specs, scripts.packages/adapters/src/vendors.ts:18
<a id="property-manifest-3"></a> manifestreadonlyobjectThe manifest of a turn the SharedOS loop drives, speaking the vendor's wire format.packages/adapters/src/vendors.ts:23
manifest.idpublicstring-packages/contracts/dist/runtime.d.ts:9
manifest.metadata?publicJsonObject-packages/contracts/dist/runtime.d.ts:12
manifest.protocolVersionpublic"1"-packages/contracts/dist/runtime.d.ts:11
manifest.versionpublicstring-packages/contracts/dist/runtime.d.ts:10
<a id="property-mcpmanifest"></a> mcpManifestreadonlyobjectThe manifest of a turn the vendor CLI runs itself, connected over MCP.packages/adapters/src/vendors.ts:25
mcpManifest.idpublicstring-packages/contracts/dist/runtime.d.ts:9
mcpManifest.metadata?publicJsonObject-packages/contracts/dist/runtime.d.ts:12
mcpManifest.protocolVersionpublic"1"-packages/contracts/dist/runtime.d.ts:11
mcpManifest.versionpublicstring-packages/contracts/dist/runtime.d.ts:10
<a id="property-protocol-1"></a> protocolreadonlyHarnessProtocol-packages/adapters/src/vendors.ts:19
<a id="property-requirements"></a> requirementsreadonlyHarnessRequirementsWhat a live session needs before it can run.packages/adapters/src/vendors.ts:21

HarnessVendorDefinition

Defined in: packages/adapters/src/vendors.ts:28

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-catalogueoutofband"></a> catalogueOutOfBand?readonlybooleanThe harness runs its own tools, so the permission-filtered catalogue cannot be declared in a frame. Stamped on every record its driven manifest produces, because a column whose catalogue arrived out of band is making a narrower claim than one whose catalogue was on the wire.packages/adapters/src/vendors.ts:41
<a id="property-credentialvariables-1"></a> credentialVariablesreadonlyreadonly string[]Environment variables, any one of which satisfies the credential need.packages/adapters/src/vendors.ts:34
<a id="property-executable-1"></a> executablereadonlystringExecutable expected on PATH.packages/adapters/src/vendors.ts:32
<a id="property-id-2"></a> idreadonly"codex" | "claude-code" | "deepseek" | "pi"-packages/adapters/src/vendors.ts:29
<a id="property-mcpmetadata"></a> mcpMetadata?readonlyJsonObjectWhat else the MCP manifest says about how this harness reaches MCP.packages/adapters/src/vendors.ts:43
<a id="property-protocol-2"></a> protocolreadonlyHarnessProtocol-packages/adapters/src/vendors.ts:30

ModelClient

Defined in: packages/adapters/src/model/client.ts:90

A model API in the SharedOS driver seat.

Deliberately narrower than any provider SDK: one call, tools in, tool calls out. Everything that decides whether a call is allowed to happen -- the catalogue, the turn loop, per-call re-authorization, audit -- stays in the execution envelope, so a second provider is a second implementation of this interface and no new enforcement path.

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-model-2"></a> modelreadonlystringThe model this client was configured to ask for.packages/adapters/src/model/client.ts:92
<a id="property-provider-2"></a> providerreadonlystringThe provider that serves it, recorded alongside the model on every turn.packages/adapters/src/model/client.ts:94
<a id="property-settings-1"></a> settings?readonlyJsonObjectSettings this client sends that change what the model does, beyond naming it -- a reasoning mode, say. Recorded on every turn beside the model so two runs under one model name are not read as the same configuration when they were not. Absent when the client sends nothing the provider would not default to.packages/adapters/src/model/client.ts:102

Methods

complete()

complete(request, signal): Promise<ModelReply>>

Defined in: packages/adapters/src/model/client.ts:103

Parameters
ParameterType
requestModelCompletionRequest
signalAbortSignal
Returns

Promise<ModelReply>


ModelCompletionRequest

Defined in: packages/adapters/src/model/client.ts:43

Properties

PropertyModifierTypeDefined in
<a id="property-messages"></a> messagesreadonlyreadonly ModelMessage[]packages/adapters/src/model/client.ts:44
<a id="property-tools-1"></a> toolsreadonlyreadonly ModelTool[]packages/adapters/src/model/client.ts:45

ModelReply

Defined in: packages/adapters/src/model/client.ts:55

What the model answered with.

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-finishreason"></a> finishReason?readonlystringWhy generation stopped, in the provider's own vocabulary. stop and tool_calls are the model ending its reply; length is the provider ending it at the output-token ceiling. Carried because the two are different facts about the same reply: a completion that was cut off mid-way looks, without this, exactly like a completion the model chose to end, and a record whose purpose is honest attribution has to tell them apart.packages/adapters/src/model/client.ts:67
<a id="property-model-3"></a> model?readonlystringThe model the provider says actually answered. Recorded separately from the one that was asked for because they differ: DeepSeek maps an unrecognised name onto a default rather than rejecting it, so a run configured for one model can be served by another. The record should say what answered, which is the weaker claim and the honest one.packages/adapters/src/model/client.ts:78
<a id="property-text"></a> textreadonlystring-packages/adapters/src/model/client.ts:56
<a id="property-toolcalls"></a> toolCallsreadonlyreadonly ModelToolCall[]-packages/adapters/src/model/client.ts:57
<a id="property-usage"></a> usage?readonlyModelUsageAbsent when the provider reported no usage; never estimated.packages/adapters/src/model/client.ts:69

ModelTool

Defined in: packages/adapters/src/model/client.ts:20

A tool offered to the model, already rendered into the provider's alphabet.

Properties

PropertyModifierTypeDefined in
<a id="property-description"></a> descriptionreadonlystringpackages/adapters/src/model/client.ts:22
<a id="property-name-1"></a> namereadonlystringpackages/adapters/src/model/client.ts:21
<a id="property-parameters"></a> parametersreadonlyJsonObjectpackages/adapters/src/model/client.ts:23

ModelToolCall

Defined in: packages/adapters/src/model/client.ts:13

One tool call a model asked for, exactly as it came off the wire.

The name is the provider's alphabet, not SharedOS's, and the arguments are still an unparsed string. Neither is normalised here: a client's job is to carry what the model said, and deciding what an unparseable argument blob or an unrecognised name means is a policy question that belongs to the driver.

Properties

PropertyModifierTypeDefined in
<a id="property-arguments"></a> argumentsreadonlystringpackages/adapters/src/model/client.ts:16
<a id="property-id-3"></a> idreadonlystringpackages/adapters/src/model/client.ts:14
<a id="property-name-2"></a> namereadonlystringpackages/adapters/src/model/client.ts:15

ModelTranscript

Defined in: packages/adapters/src/model/transcript.ts:11

A model conversation, supplied by its caller.

One reply per model call. The first reply answers the opening prompt, and each later reply is released only once every tool call in the reply before it has been answered -- which is what the driver already requires of a live provider, so a transcript exercises the same code path a live model does.

Properties

PropertyModifierTypeDefined in
<a id="property-replies"></a> repliesreadonlyreadonly ModelReply[]packages/adapters/src/model/transcript.ts:12

ModelUsage

Defined in: packages/adapters/src/model/client.ts:49

What a provider billed for one reply, when it said.

Properties

PropertyModifierTypeDefined in
<a id="property-inputtokens"></a> inputTokens?readonlynumberpackages/adapters/src/model/client.ts:50
<a id="property-outputtokens"></a> outputTokens?readonlynumberpackages/adapters/src/model/client.ts:51

OpenAiCompatibleModelClientOptions

Defined in: packages/adapters/src/model/client.ts:157

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-apikey"></a> apiKeyreadonlystring-packages/adapters/src/model/client.ts:158
<a id="property-baseurl"></a> baseUrlreadonlystringThe chat-completions root, without a trailing slash.packages/adapters/src/model/client.ts:163
<a id="property-fetch"></a> fetch?readonly{(input, init?): Promise<Response>; (input, init?): Promise<Response>; }Injected for tests, which must never reach a network.packages/adapters/src/model/client.ts:183
<a id="property-maxoutputtokens"></a> maxOutputTokens?readonlynumber-packages/adapters/src/model/client.ts:164
<a id="property-model-4"></a> modelreadonlystring-packages/adapters/src/model/client.ts:159
<a id="property-provider-3"></a> providerreadonlystringNames the provider on every record this client's turns produce.packages/adapters/src/model/client.ts:161
<a id="property-requesttimeoutms"></a> requestTimeoutMs?readonlynumberHow long one model call may take, independently of the turn's own budget.packages/adapters/src/model/client.ts:173
<a id="property-temperature"></a> temperature?readonlynumberLeft at zero by default, which reduces variation between runs but does not remove it. This column is not deterministic and must not be described as if it were: a temperature of zero is not a seed, and the same prompt can still produce a different call sequence on a different day.packages/adapters/src/model/client.ts:171
<a id="property-thinking"></a> thinking?readonly"enabled" | "disabled"Whether the model reasons before it answers, sent as DeepSeek's thinking request field. Opt-in: the field is not part of the OpenAI wire shape, and a provider that does not know it rejects the request, so nothing is sent until a host asks. When set it is reported through ModelClient.settings so the turn's record says which mode the model ran in.packages/adapters/src/model/client.ts:181

SeatTextOptions

Defined in: packages/adapters/src/seat.ts:25

How a turn's message and reach become the two texts a seat is handed.

Extended by

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-instructions-2"></a> instructions?readonlystring | ((request) => string | undefined)What the seat is told before the prompt: a model's system message, an MCP server's initialize instructions, a harness's preamble. By default it is request.context.reach rendered by describeReach: where this turn's tools may operate, with the authority left out. The prompt carries the task and this carries the environment the task runs in. A string is the host's standing guidance, placed before the turn's reach so a seat that shows its model one block reads the guidance before the map. A function replaces the composition and says exactly what the seat is told; returning undefined hands over no instructions at all.packages/adapters/src/seat.ts:40
<a id="property-prompt-2"></a> prompt?readonly(request) => stringOverrides how the turn message becomes the prompt.packages/adapters/src/seat.ts:27

StandardTurnDriverOptions

Defined in: packages/adapters/src/model/driver.ts:95

instructions reaches the model as a system message, which is what a chat-completions provider's system role is for, and it is the same layer a harness maps MCP initialize instructions into. With none, no system message is sent.

Extends

Properties

PropertyModifierTypeDescriptionInherited fromDefined in
<a id="property-client"></a> clientreadonlyModelClient--packages/adapters/src/model/driver.ts:97
<a id="property-declarestep-1"></a> declareStep?readonlyDeclareStepSee DeclareStep.-packages/adapters/src/model/driver.ts:108
<a id="property-instructions-3"></a> instructions?readonlystring | ((request) => string | undefined)What the seat is told before the prompt: a model's system message, an MCP server's initialize instructions, a harness's preamble. By default it is request.context.reach rendered by describeReach: where this turn's tools may operate, with the authority left out. The prompt carries the task and this carries the environment the task runs in. A string is the host's standing guidance, placed before the turn's reach so a seat that shows its model one block reads the guidance before the map. A function replaces the composition and says exactly what the seat is told; returning undefined hands over no instructions at all.SeatTextOptions.instructionspackages/adapters/src/seat.ts:40
<a id="property-manifest-4"></a> manifestreadonlyobject--packages/adapters/src/model/driver.ts:96
manifest.idpublicstring--packages/contracts/dist/runtime.d.ts:9
manifest.metadata?publicJsonObject--packages/contracts/dist/runtime.d.ts:12
manifest.protocolVersionpublic"1"--packages/contracts/dist/runtime.d.ts:11
manifest.versionpublicstring--packages/contracts/dist/runtime.d.ts:10
<a id="property-maxmalformedcalls"></a> maxMalformedCalls?readonlynumberGuard against a model that never forms a readable call. A call whose arguments do not parse is refused by the driver and answered back to the model, which costs the turn no step; a model that kept producing them would otherwise be spoken to until the turn timed out. Past this many in one turn, the turn fails instead.-packages/adapters/src/model/driver.ts:106
<a id="property-prompt-3"></a> prompt?readonly(request) => stringOverrides how the turn message becomes the prompt.SeatTextOptions.promptpackages/adapters/src/seat.ts:27

TranscriptModelClientOptions

Defined in: packages/adapters/src/model/transcript.ts:15

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-model-5"></a> model?readonlystringWhat the record names as the model; defaults to transcript.packages/adapters/src/model/transcript.ts:17
<a id="property-provider-4"></a> provider?readonlystringWhat the record names as the provider; defaults to transcript.packages/adapters/src/model/transcript.ts:19

Type Aliases

DeclareStep

DeclareStep = (index, request) => number | undefined

Defined in: packages/adapters/src/seat.ts:111

The step to declare for the nth call a driver releases this turn, if any.

undefined, the default for every call, leaves the step to the loop, which is what a driver asking for one call at a time should do. It exists for the one thing a driver cannot otherwise express: reaching past its own budget. The loop's index stops at maxSteps, so a call at or past the ceiling can only be made by a driver that names the step itself. Supplying this makes the driver the attacker for that call, which is a different claim from the seat choosing it, and a column that uses it should say so.

Parameters

ParameterType
indexnumber
requestRuntimeTurnRequest

Returns

number | undefined


HarnessFrame

HarnessFrame = JsonObject

Defined in: packages/adapters/src/harness.ts:11

One raw protocol frame, in whatever shape the harness speaks.


HarnessStep

HarnessStep = { arguments: JsonObject; callId: string; tool: string; type: "tool_call"; } | { text: string; type: "message"; } | { metadata?: JsonObject; output?: JsonValue; type: "complete"; } | { error: ProtocolError; type: "failed"; }

Defined in: packages/adapters/src/harness.ts:19

What one frame means once the vendor protocol has interpreted it.

message is assistant prose. It is kept rather than discarded so a harness whose terminal frame carries no text still produces a turn output.


ModelMessage

ModelMessage = { content: string; role: "system" | "user"; } | { content: string; role: "assistant"; toolCalls: readonly ModelToolCall[]; } | { content: string; role: "tool"; toolCallId: string; }

Defined in: packages/adapters/src/model/client.ts:34

One turn of conversation.

assistant carries the tool calls the model asked for and tool carries one result back, because a chat-completions provider requires the pair to appear in that order and requires every call in an assistant message to be answered before the next one is sent.


ModelToolCallReading

ModelToolCallReading = { reason: string; type: "escalate"; } | { call: ToolCall; type: "tool_call"; } | { refusal: ToolResult; type: "malformed"; }

Defined in: packages/adapters/src/model/driver.ts:413

Where one call the model asked for goes once it has been read.

Union Members

Type Literal

{ reason: string; type: "escalate"; }


Type Literal

{ call: ToolCall; type: "tool_call"; }


Type Literal

{ refusal: ToolResult; type: "malformed"; }

refusal

readonly refusal: ToolResult

The refusal the model is shown for a call made with unreadable arguments.

type

readonly type: "malformed"


SeatMetadata

SeatMetadata = object

Defined in: packages/adapters/src/seat.ts:202

What a seat states about its turn, on the turn's result metadata.

One vocabulary for every seat here, so a reader of ExecutionResult.metadata finds the same fact under the same key whichever seat ran. A seat states the keys that apply to it and leaves the rest absent; an absent key is "not stated", never zero. The envelope adds its own beside these (runtime, promptHash, escalationAsked).

The conformance record lifts model, modelProvider, catalogHash, the token counts and callsAfterEscalation. The rest are for the host that ran the turn.

Properties

callsAfterEscalation?

readonly optional callsAfterEscalation?: number

Defined in: packages/adapters/src/seat.ts:249

On a turn that ended escalated over MCP: calls the harness made after its ask. Each was answered escalation_pending and reached neither the envelope nor the kernel, so this is the only place they can be read.

catalogHash?

readonly optional catalogHash?: string

Defined in: packages/adapters/src/seat.ts:233

The catalogue the harness was served, so a run can prove which tool set it received.

finishReason?

readonly optional finishReason?: string

Defined in: packages/adapters/src/seat.ts:217

Why the last reply ended, in the provider's words.

harness?

readonly optional harness?: string

Defined in: packages/adapters/src/seat.ts:227

The vendor harness that ran, by its id.

harnessErrorCode?

readonly optional harnessErrorCode?: string

Defined in: packages/adapters/src/seat.ts:243

With harnessOutcome: "fail", the code the harness failed under.

harnessOutcome?

readonly optional harnessOutcome?: "complete" | "fail" | "escalate"

Defined in: packages/adapters/src/seat.ts:241

On a turn that ended escalated over MCP: how the harness itself ended. "The CLI reported success after asking" and "the CLI crashed after asking" are different runs.

inputTokens?

readonly optional inputTokens?: number

Defined in: packages/adapters/src/seat.ts:219

Summed over every model call this turn; absent until a reply reports one.

malformedToolCalls?

readonly optional malformedToolCalls?: number

Defined in: packages/adapters/src/seat.ts:225

Calls the standard driver refused in place because their arguments were not a JSON object. None of them reached the envelope, so no operation shows them.

mcpServer?

readonly optional mcpServer?: string

Defined in: packages/adapters/src/seat.ts:231

The MCP server name the harness namespaced its aliases under.

model?

readonly optional model?: string

Defined in: packages/adapters/src/seat.ts:209

The model behind the seat. The standard driver states the one the provider served, because a provider may substitute; the MCP harness runtime states the one the run declared, because a vendor CLI selects its own model and SharedOS cannot confirm which answered.

modelProvider?

readonly optional modelProvider?: string

Defined in: packages/adapters/src/seat.ts:211

Who served it (standard driver), or who the run declared (MCP harness runtime).

modelSettings?

readonly optional modelSettings?: JsonObject

Defined in: packages/adapters/src/seat.ts:215

The sampling settings the model client was configured with.

outputTokens?

readonly optional outputTokens?: number

Defined in: packages/adapters/src/seat.ts:220

requestedModel?

readonly optional requestedModel?: string

Defined in: packages/adapters/src/seat.ts:213

The model the standard driver asked for, when the served one may differ.

toolAliases?

readonly optional toolAliases?: object[]

Defined in: packages/adapters/src/seat.ts:235

Names the harness rewrote, for reading its transcript back; never an authorization input.

alias

readonly alias: string

tool

readonly tool: string

toolshare?

readonly optional toolshare?: "mcp"

Defined in: packages/adapters/src/seat.ts:229

How the harness reached the catalogue.

Variables

CLAUDE_CODE_HARNESS_ID

const CLAUDE_CODE_HARNESS_ID: "codex" | "claude-code" | "deepseek" | "pi" = CLAUDE_CODE_VENDOR.id

Defined in: packages/adapters/src/vendors.ts:140


CLAUDE_CODE_PROTOCOL_ID

const CLAUDE_CODE_PROTOCOL_ID: "anthropic.messages.stream-json" = "anthropic.messages.stream-json"

Defined in: packages/adapters/src/claude-code/protocol.ts:16

Claude Code speaks Anthropic message content blocks inside a stream-json envelope.

The content blocks -- tool_use, tool_result, text -- are the stable part and are what this module translates. The {type:"assistant"|"user"| "result"} envelope is the CLI's --output-format stream-json framing.


CLAUDE_CODE_REQUIREMENTS

const CLAUDE_CODE_REQUIREMENTS: HarnessRequirements = CLAUDE_CODE_VENDOR.requirements

Defined in: packages/adapters/src/vendors.ts:142


CLAUDE_CODE_RUNTIME_MANIFEST

const CLAUDE_CODE_RUNTIME_MANIFEST: RuntimeManifest = CLAUDE_CODE_VENDOR.manifest

Defined in: packages/adapters/src/vendors.ts:141


CLAUDE_CODE_VENDOR

const CLAUDE_CODE_VENDOR: HarnessVendor

Defined in: packages/adapters/src/vendors.ts:107


claudeCodeFrameWriter

const claudeCodeFrameWriter: HarnessFrameWriter

Defined in: packages/adapters/src/writer.ts:58

Frames in the Anthropic content-block shape Claude Code speaks.


claudeCodeProtocol

const claudeCodeProtocol: HarnessProtocol

Defined in: packages/adapters/src/claude-code/protocol.ts:57


CODEX_HARNESS_ID

const CODEX_HARNESS_ID: "codex" | "claude-code" | "deepseek" | "pi" = CODEX_VENDOR.id

Defined in: packages/adapters/src/vendors.ts:136


CODEX_PROTOCOL_ID

const CODEX_PROTOCOL_ID: "openai.responses.function-calling" = "openai.responses.function-calling"

Defined in: packages/adapters/src/codex/protocol.ts:26

Codex is read in two shapes, for the two ways it is reached.

The OpenAI Responses function-calling shape serves a transport that speaks the API directly, and the scripted conformance column: function tool declarations, function_call items, function_call_output results. The CLI in JSON mode (codex exec --json), which CODEX_MCP_HARNESS (mcp-runtime) launches, prints none of that on stdout: a turn there is thread.started, turn.started, a series of item.* events and a turn.completed or turn.failed, the model's prose is an agent_message item, and its tool calls travel over MCP. A protocol that read only the Responses shape completed a turn Codex answered in prose with nothing to show, and the host heard silence.

The CLI's envelope is the vendor's to change, so only the events a turn's outcome turns on are read; the rest are progress and yield nothing. A frame of neither shape yields nothing as well.


CODEX_REQUIREMENTS

const CODEX_REQUIREMENTS: HarnessRequirements = CODEX_VENDOR.requirements

Defined in: packages/adapters/src/vendors.ts:138


CODEX_RUNTIME_MANIFEST

const CODEX_RUNTIME_MANIFEST: RuntimeManifest = CODEX_VENDOR.manifest

Defined in: packages/adapters/src/vendors.ts:137


CODEX_VENDOR

const CODEX_VENDOR: HarnessVendor

Defined in: packages/adapters/src/vendors.ts:100

The four vendor harnesses SharedOS ships a codec for.

A vendor CLI is seated in a product through createMcpHarnessRuntime, which reads its MCP manifest here. Its wire codec is seated only by evaluation, behind EvalHarnessDriver, and that turn is filed under manifest.


codexFrameWriter

const codexFrameWriter: HarnessFrameWriter

Defined in: packages/adapters/src/writer.ts:31

Frames in the OpenAI Responses function-calling shape Codex speaks.


codexProtocol

const codexProtocol: HarnessProtocol

Defined in: packages/adapters/src/codex/protocol.ts:97


DEEPSEEK_HARNESS_ID

const DEEPSEEK_HARNESS_ID: "codex" | "claude-code" | "deepseek" | "pi" = DEEPSEEK_VENDOR.id

Defined in: packages/adapters/src/vendors.ts:144


DEEPSEEK_PROTOCOL_ID

const DEEPSEEK_PROTOCOL_ID: "deepseek.harness.session-events" = "deepseek.harness.session-events"

Defined in: packages/adapters/src/deepseek/protocol.ts:27

DeepSeek Harness speaks its own session-log vocabulary over a newline-delimited JSON-RPC 2.0 stdio transport.

That vocabulary is the layer this module targets: tool/call carrying the model's raw argument string, assistant/message carrying assembled content blocks, and turn/end carrying a structured reason. It is deliberately not the dsh CLI's command-line surface, which is a plugin composition that varies per deployment. What carries these frames -- the SDK runtime server, the ACP bridge, or a recorded log -- is the transport's problem.

One asymmetry is worth stating plainly, because it is a property of the harness rather than of this adapter. DeepSeek Harness executes its own tools: its wire has no frame meaning "here is your catalogue". A host that wants the catalogue to be the permission-filtered one must deliver it out of band, and the harness's own path for that is an MCP server (dsh-mcp-client). So HarnessProtocol.describeTools renders the harness's ToolSchema shape, which is what that out-of-band channel carries, and no frame is emitted for it.


DEEPSEEK_REQUIREMENTS

const DEEPSEEK_REQUIREMENTS: HarnessRequirements = DEEPSEEK_VENDOR.requirements

Defined in: packages/adapters/src/vendors.ts:146


DEEPSEEK_RUNTIME_MANIFEST

const DEEPSEEK_RUNTIME_MANIFEST: RuntimeManifest = DEEPSEEK_VENDOR.manifest

Defined in: packages/adapters/src/vendors.ts:145


DEEPSEEK_VENDOR

const DEEPSEEK_VENDOR: HarnessVendor

Defined in: packages/adapters/src/vendors.ts:114


deepseekFrameWriter

const deepseekFrameWriter: HarnessFrameWriter

Defined in: packages/adapters/src/writer.ts:92

Frames in the session-log shape DeepSeek Harness streams.

Wrapped in their session.event notification rather than left bare, because that is the shape a live SDK runtime emits and a fixture that skipped the envelope would exercise only half of what the parser has to accept.


deepseekProtocol

const deepseekProtocol: HarnessProtocol

Defined in: packages/adapters/src/deepseek/protocol.ts:102


PI_HARNESS_ID

const PI_HARNESS_ID: "codex" | "claude-code" | "deepseek" | "pi" = PI_VENDOR.id

Defined in: packages/adapters/src/vendors.ts:148


PI_PROTOCOL_ID

const PI_PROTOCOL_ID: "pi.rpc.jsonl" = "pi.rpc.jsonl"

Defined in: packages/adapters/src/pi/protocol.ts:31

Pi speaks newline-delimited JSON events in its RPC mode (pi --mode rpc).

The message vocabulary is the layer this module targets: an AssistantMessage whose content carries toolCall blocks, a ToolResultMessage carrying the result back, and the agent_end / response frames that end a turn. It is deliberately not Pi's streaming delta events, which restate the same content token by token; Pi's own guidance is to treat the assembled message as authoritative, and reading both would issue every call twice.

Two asymmetries are worth stating plainly, because both are properties of the harness rather than of this adapter:

  • Pi does not declare tools on the RPC wire, and ships no MCP client of its own. Its path for a host-supplied tool is defineTool through the SDK, or an extension such as pi-mcp-adapter, which is how the MCP column reaches it; HarnessProtocol.describeTools renders the defineTool shape and no frame is emitted for it.
  • Pi executes its own tools. tool_execution_start announces a call Pi is already running, not a request for the host to run one, so it is not read as a tool call. The toolCall content block -- the model's actual request -- is.

PI_REQUIREMENTS

const PI_REQUIREMENTS: HarnessRequirements = PI_VENDOR.requirements

Defined in: packages/adapters/src/vendors.ts:150


PI_RUNTIME_MANIFEST

const PI_RUNTIME_MANIFEST: RuntimeManifest = PI_VENDOR.manifest

Defined in: packages/adapters/src/vendors.ts:149


PI_VENDOR

const PI_VENDOR: HarnessVendor

Defined in: packages/adapters/src/vendors.ts:122


piFrameWriter

const piFrameWriter: HarnessFrameWriter

Defined in: packages/adapters/src/writer.ts:135

Frames in the RPC message shape Pi speaks.


piProtocol

const piProtocol: HarnessProtocol

Defined in: packages/adapters/src/pi/protocol.ts:95

Functions

decodeChatCompletion()

decodeChatCompletion(payload): ModelReply | undefined

Defined in: packages/adapters/src/model/client.ts:331

One chat-completions response body, read into what the driver needs of it.

undefined is a body the schema refused, and the caller decides what that means -- the client turns it into a request error. It is a function rather than a method so the read can be measured on its own: this is the native harness's frame parse, the counterpart of a vendor adapter's interpret, and the bench charges it per call the way it charges the others.

Parameters

ParameterType
payloadunknown

Returns

ModelReply | undefined


defineHarnessVendor()

defineHarnessVendor(definition): HarnessVendor

Defined in: packages/adapters/src/vendors.ts:55

One vendor's manifests and requirements from its few facts.

Every harness here can also authenticate from a session it stored itself (codex login, a Claude subscription, dsh's credentials file, Pi's auth.json), so credentials are optional for all of them.

Parameters

ParameterType
definitionHarnessVendorDefinition

Returns

HarnessVendor


encodeModelMessage()

encodeModelMessage(message): JsonObject

Defined in: packages/adapters/src/model/client.ts:363

One message in the shape the provider's wire carries it.

Parameters

ParameterType
messageModelMessage

Returns

JsonObject


harnessTurnText()

harnessTurnText(request): string

Defined in: packages/adapters/src/harness.ts:52

The instructions and the prompt as one text, instructions first, for a harness whose opening frame carries a single message.

Parameters

ParameterType
request{ instructions?: string; prompt: string; }
request.instructions?string
request.promptstring

Returns

string


modelToolResultMessage()

modelToolResultMessage(result): ModelMessage

Defined in: packages/adapters/src/model/driver.ts:465

The message that answers one call, in the shape the model reads it back.

Parameters

ParameterType
result{ callId: string; completedAt: string; metadata?: JsonObject; output: JsonValue; status: "succeeded"; tool: string; } | { callId: string; completedAt: string; error: { code: string; details?: JsonObject; message: string; retryable?: boolean; }; metadata?: JsonObject; status: "denied"; tool: string; } | { callId: string; completedAt: string; error: { code: string; details?: JsonObject; message: string; retryable?: boolean; }; metadata?: JsonObject; status: "failed"; tool: string; }

Returns

ModelMessage


readModelToolCall()

readModelToolCall(call, codec, offered, context): ModelToolCallReading

Defined in: packages/adapters/src/model/driver.ts:434

Read one call off a reply: the provider's alphabet back to the catalogue's, the argument blob parsed, and the escalate affordance recognised by name when the turn was offered it. #release on the session says what each answer means and why; this is only the reading.

A function rather than a method because the native harness has a translation layer like any vendor adapter -- this, and the reply decode in the client -- and the bench charges it per call the way it charges the others. Measuring a copy of the driver's logic would not be measuring the driver, so the session calls exactly this.

Parameters

ParameterType
callModelToolCall
codecToolNameCodec
offeredboolean
context{ now: string; traceId: string; }
context.nowstring
context.traceIdstring

Returns

ModelToolCallReading