@@ -51,6 +51,12 @@ export interface SessionAuth {
5151export interface CreateSessionInput extends SessionAuth {
5252 agentId : string
5353 environmentId : string
54+ /**
55+ * Environment execution model. Self-hosted environments reject the
56+ * `resources` array, so memory is routed via `metadata` and files are
57+ * dropped for them. Defaults to cloud behavior when unset.
58+ */
59+ environmentType ?: EnvironmentType
5460 /** Optional session title stored on the Anthropic session. */
5561 title ?: string
5662 /** OAuth credential vaults the agent's MCP tools can reference. */
@@ -77,6 +83,9 @@ export interface SessionUsage {
7783 outputTokens ?: number
7884}
7985
86+ /** Environment execution model per `GET /v1/environments/{id}` → `config.type`. */
87+ export type EnvironmentType = 'cloud' | 'self_hosted'
88+
8089/** Authoritative session status per `GET /v1/sessions/{id}`. */
8190export type SessionStatus = 'idle' | 'running' | 'rescheduling' | 'terminated'
8291
@@ -105,10 +114,14 @@ function managedAgentsHeaders(
105114}
106115
107116/**
108- * Builds the request body for `POST /v1/sessions`. Memory stores and files
109- * attach via the `resources[]` array; user key/values go on `metadata`. This
110- * shape is env-type agnostic — the same body works for cloud and self-hosted
111- * environments per the docs.
117+ * Builds the request body for `POST /v1/sessions`.
118+ *
119+ * Cloud environments attach memory stores and files via the `resources[]`
120+ * array. Self-hosted environments REJECT `resources` (a documented 400 —
121+ * "resources are not supported with self-hosted environments"), so there the
122+ * memory store is surfaced to the worker via `metadata.memory_store_ids` /
123+ * `metadata.memory_access` and files are dropped. Session parameters always go
124+ * on `metadata` for both.
112125 */
113126export function buildSessionCreatePayload ( input : CreateSessionInput ) : Record < string , unknown > {
114127 const payload : Record < string , unknown > = {
@@ -118,29 +131,38 @@ export function buildSessionCreatePayload(input: CreateSessionInput): Record<str
118131 if ( input . title ) payload . title = input . title
119132 if ( input . vaultIds && input . vaultIds . length > 0 ) payload . vault_ids = input . vaultIds
120133
121- const resources : Array < Record < string , unknown > > = [ ]
122- if ( input . memoryStoreId ) {
123- const memory : Record < string , unknown > = {
124- type : 'memory_store' ,
125- memory_store_id : input . memoryStoreId ,
126- access : input . memoryAccess ?? 'read_write' ,
134+ const metadata : Record < string , string > = { ...( input . sessionParameters ?? { } ) }
135+
136+ if ( input . environmentType === 'self_hosted' ) {
137+ // No `resources` on self-hosted — route memory through metadata (the
138+ // worker consumes these keys) and drop file attachments.
139+ if ( input . memoryStoreId ) {
140+ metadata . memory_store_ids = input . memoryStoreId
141+ metadata . memory_access = input . memoryAccess ?? 'read_write'
127142 }
128- if ( input . memoryInstructions ) memory . instructions = input . memoryInstructions
129- resources . push ( memory )
130- }
131- if ( input . files && input . files . length > 0 ) {
132- for ( const file of input . files ) {
133- if ( ! file . fileId ) continue
134- const entry : Record < string , unknown > = { type : 'file' , file_id : file . fileId }
135- if ( file . mountPath ) entry . mount_path = file . mountPath
136- resources . push ( entry )
143+ } else {
144+ const resources : Array < Record < string , unknown > > = [ ]
145+ if ( input . memoryStoreId ) {
146+ const memory : Record < string , unknown > = {
147+ type : 'memory_store' ,
148+ memory_store_id : input . memoryStoreId ,
149+ access : input . memoryAccess ?? 'read_write' ,
150+ }
151+ if ( input . memoryInstructions ) memory . instructions = input . memoryInstructions
152+ resources . push ( memory )
153+ }
154+ if ( input . files && input . files . length > 0 ) {
155+ for ( const file of input . files ) {
156+ if ( ! file . fileId ) continue
157+ const entry : Record < string , unknown > = { type : 'file' , file_id : file . fileId }
158+ if ( file . mountPath ) entry . mount_path = file . mountPath
159+ resources . push ( entry )
160+ }
137161 }
162+ if ( resources . length > 0 ) payload . resources = resources
138163 }
139- if ( resources . length > 0 ) payload . resources = resources
140164
141- if ( input . sessionParameters && Object . keys ( input . sessionParameters ) . length > 0 ) {
142- payload . metadata = { ...input . sessionParameters }
143- }
165+ if ( Object . keys ( metadata ) . length > 0 ) payload . metadata = metadata
144166 return payload
145167}
146168
@@ -344,6 +366,30 @@ export async function managedAgentsList<T>(
344366 } )
345367}
346368
369+ /**
370+ * GET /v1/environments/{id} — resolves the environment's execution model from
371+ * `config.type`. Drives session-payload routing: self-hosted rejects
372+ * `resources`. Returns `undefined` on any error so the caller can fall back to
373+ * cloud behavior.
374+ */
375+ export async function getEnvironmentType (
376+ input : SessionAuth & { environmentId : string }
377+ ) : Promise < EnvironmentType | undefined > {
378+ try {
379+ const resp = await fetch ( `${ ANTHROPIC_API_BASE } /v1/environments/${ input . environmentId } ` , {
380+ method : 'GET' ,
381+ headers : managedAgentsHeaders ( input . apiKey ) ,
382+ signal : input . signal ,
383+ } )
384+ if ( ! resp . ok ) return undefined
385+ const body = ( await resp . json ( ) ) as { config ?: { type ?: unknown } }
386+ const type = body . config ?. type
387+ return type === 'cloud' || type === 'self_hosted' ? type : undefined
388+ } catch {
389+ return undefined
390+ }
391+ }
392+
347393/**
348394 * GET /v1/sessions/{id} — retrieves the session resource. Returns the
349395 * authoritative `status` (used to decide completion when the event stream is
0 commit comments