Upgrade TypeScript to 0.7
Every breaking change in `cosmo-ai` 0.7.0, with the replacement for each.
Breaking changes when moving cosmo-ai from v0.6.0 to v0.7.0. The changelog has the full release notes; more than one version behind, chain the pages.
-
TranscriptDeltaEventis now the wire shape —{ role, text, isFinal }. The client-derivedid,turnId, andappendfields are removed: they were rendering instructions, and the session's coalesced transcript —session.transcript, oneTranscriptItemper turn with a stableid— is the render-ready form. -
tool()is renamedclientTool(), and the background form is its own constructor rather than a flag:tool({ background: true, ... })becomesbackgroundClientTool({ ... }), andbackgroundis gone from the option types — the constructor decides the form, so a caller can no longer set it to the value contradicting the one they called. The typed, raw and unsafe input forms are unchanged; they stay overloads of each constructor.// before const lookup = tool({ name: 'lookup', description: '…', parameters, handler }); const index = tool({ name: 'index', description: '…', parameters, handler, background: true }); // after — the constructor decides the form const lookup = clientTool({ name: 'lookup', description: '…', parameters, handler }); const index = backgroundClientTool({ name: 'index', description: '…', parameters, handler }); -
Tools are built by calling a constructor, and every constructor returns
AgentTool.{ kind: 'web_search' }and the other hand-written literals are no longer the documented form —AgentToolis a structural union, so an existing literal still compiles, but it is unsupported and gains none of the constructors' checks. UsewebSearchTool(),examineImageTool(),detectObjectsTool(),pointAtObjectTool(),endCallTool(),screenLocateTool(capture). The SDK-shipped renderers gained the same suffix:drawBox→drawBoxTool,drawPoint→drawPointTool,screenClickElement→screenClickElementTool,screenHighlightElement→screenHighlightElementTool,screenHighlightBox→screenHighlightBoxTool. The per-tool types (ClientToolSpec,WebSearchToolSpec, and the rest) are no longer exported — annotate withAgentTool, whichRealtimeToolis also renamed to.// before client.agent({ tools: [{ kind: 'web_search' }, { kind: 'examine_image' }] }); // after client.agent({ tools: [webSearchTool(), examineImageTool()] }); -
The agent's output level is metered from its audio track instead of the
<audio>element playing it, so a customRealtimeTransportmust implement the new optionalgetOutputStreamandonOutputStreamChangedto report an output level. It keeps compiling and stays audible without them; onlyuseOutputLevelgoes quiet. The element tap it replaces claimed the element for the life of the page and routed its sound through the audio graph, so ending a session left the element wired to a closed graph and the next session played silently.A custom transport adds the two optional members:
getOutputStream?(): MediaStream | null; // the remote agent audio onOutputStreamChanged?(cb: () => void): Unsubscribe; // fires when it is replaced -
audio.noiseCancellationtakes a mode instead of a boolean —'off','denoise'or'voice_focus'. The new one is'denoise': it removes non-speech noise and keeps every voice, which is what a microphone several people share needs.'voice_focus'is the previous behaviour, and keeps only the speaker it judges primary — on a shared microphone that treats the second person as background and filters them out.trueandfalseremain valid on the wire, so a session started by an already-published SDK version is unaffected.truebecomes'voice_focus',falsebecomes'off'. A two-person setup that was passingtrueand losing the quieter speaker wants'denoise':// before client.agent({ audio: { noiseCancellation: true } }); // after — same behaviour client.agent({ audio: { noiseCancellation: 'voice_focus' } }); // after — noise goes, both voices stay client.agent({ audio: { noiseCancellation: 'denoise' } }); -
Passing a workspace API key (
cosmo_…) astokenis refused at construction in every SDK. That parameter takes a minted end-user token; a key there authenticates anyway, so the mistake used to work — and shipped the key with whatever app carried it. Pass the key as the API-key parameter, or mint a token for the user withmintTokenand pass that. Acts-as-user tokens (cosmo_pat_…) are unaffected.// before new RealtimeClient({ token: apiKey }); // after new RealtimeClient({ apiKey }); -
The client-tool option types are named and exported:
ClientToolOptionsandRawClientToolOptions, each taking the handler as a type parameter — the handler is the only thing the immediate and background constructors differ by, so there is one type per input form rather than one per form per constructor.ToolInputstops being exported — a converter mints it, so it is not a type a caller writes. -
The Standard Schema input form is removed:
clientToolandbackgroundClientToolno longer take{ input: <validator>, unsafeParameters }, andStandardSchemaV1,ToolInputandToolInputParseResultare no longer exported. It existed so a validator the SDK ships no converter for could still validate handler arguments, at the cost of publishing the interop types and a third call shape nothing used. A validator without a converter goes through{ parameters }— the hand-written schema form — and is called inside the handler. The two remaining forms are the two Python and Swift take.The typed
inputform (a converter-minted schema) is unchanged; this is the removed unsafe form's migration:// before — the unsafe form: a Standard Schema validator beside a hand-written schema tool({ name: 'lookup', description: '…', input: lookupSchema, unsafeParameters: lookupJsonSchema, handler }); // after — keep the hand-written parameters; run the validator inside the handler clientTool({ name: 'lookup', description: '…', parameters: lookupJsonSchema, handler: async (args) => handle(lookupSchema.parse(args)), }); -
handleris required on the raw form ofclientTool({ parameters })andbackgroundClientTool({ parameters }), matching the typed and Standard Schema forms, which always required it. A tool declared without one was advertised to the agent and then failed every time it was called. -
model_optionsis gone; its provider block moves ontomodel, which now takes either the model string it always took or one provider block naming the provider once — a model that disagrees with its knobs is unrepresentable. The provider types drop theOptionssuffix (GeminiModelOptions→GeminiModel, and likewise for OpenAI, OpenAI-mini and Grok), each block'sturn_detectionaccepts only the detectors its provider offers (Cosmo-VAD tuning moves toCosmoVadConfigon the block'scosmoVadfield), and the union is namedRealtimeModel:ModelOptionsis renamed, taking either the string or the block, withRealtimeModelBlockfor the block alone. A block with no model id runs the provider's default, andmodel: "gemini"still selects a provider by name. The server keeps acceptingmodel_optionsfrom existing releases — the old pair folds intomodelserver-side — so upgrading the SDK is not coupled to a backend deploy.Move the block to
modeland fold the old model string into itsmodel_id:// before client.agent({ model: 'gemini-live', modelOptions: { provider: 'gemini', temperature: 0.7 } }); // after — the constructor stamps the provider tag client.agent({ model: GeminiModel({ modelId: 'gemini-live', temperature: 0.7 }) }); -
SkillParseErroris nowSkillErrorand carries acodenaming which failure it was, so a caller can tell them apart without reading the message. The codes arenot_a_directory,cannot_read,missing_frontmatter,unterminated_frontmatter,malformed_frontmatter_line,duplicate_frontmatter_key,missing_descriptionandduplicate_skill_name— aSkillErrorCodeenum in Python and Swift, a union of the same values in TypeScript. The old name described only the five parsing failures, while the type has always also covered a bad path and a duplicate skill name. Match onerr.code;err.messageis the sentence alone, and an unreadable skills directory now raisesSkillError(cannot_read)where it used to escape as a barePermissionError.Breaking: Attaching skills reads the same in every SDK. Swift takes a directory through the
skills:argument itself —client.agent(skills: .directory(skillsURL))— instead ofloadSkills(fromDirectory:), which is no longer public;.directory(_:)is a factory on[Skill], so inline skills are unchanged and the two compose with+. Swift'sskillsis optional rather than defaulting to an empty array.parseSkillMdtakesdefaultNamedirectly in TypeScript rather than wrapped in an options object, andparse_skill_mdis now public in Python for SKILL.md text you already hold. The skill-assembly internals —resolveSkills,skillsMenuText,buildLoadSkillTool,LoadSkillWiring,loadSkillToolName,UnknownSkillError— are no longer public in Swift; the wire name the tool registers under is unchanged, so a hook matchingcosmo_sdk_load_skillkeeps working. -
RealtimeAgentexposes its resolved persona as fields —agent.instructions,agent.skills,agent.voice,agent.tools,agent.model,agent.audio,agent.greeting,agent.hooks,agent.interruptionSensitivity,agent.name,agent.inputs— instead of nesting them underagent.config, which is removed. Readagent.instructionswhere you readagent.config.instructions. Building a persona is unchanged:client.agent({ instructions, voice })still takes an options object, since JavaScript has no named arguments. This matches the Python and Swift SDKs, which have always flattened the same fields onto the agent. -
MintTokenError.codeis now a closedMintTokenErrorCodenaming what the SDK saw —request_failed,invalid_response,request_rejectedormissing_api_key— and the server's own rejection slug moves toserver_code, set only when the code isrequest_rejected. The two were previously the same field, socodecould hold either the SDK's category or anything the server sent, down to a synthetichttp_<status>, with no way to tell which. Match onerr.codefor what happened to the request and readerr.server_codefor why the server refused. Handlers comparingcodeagainst"transport_error"or against a server slug such as"auth_failed"need updating;str(err)is now the message alone, without thecode:prefix.Breaking: A
TokenSourcethat cannot produce a token now raisesTokenSourceErrorrather thanMintTokenError, with its ownTokenSourceErrorCode—request_failed,request_rejected,invalid_responseorfetcher_failed. Resolving a token source is not part ofmint_token: it happens beneath every authenticated call —verify,mint_token, session start, dial and usage reads all resolve it first, and it re-resolves on expiry and after a 401 — so the failure surfaced under the name of one operation it mostly had nothing to do with.token_source_failedis gone fromMintTokenErrorCodeaccordingly, and the four new codes say which part failed where one bucket said only that something did. A refused redirect isrequest_failedin every SDK — it never reached a token endpoint, so there is no rejection to report; Python previously reported it as anhttp_<status>rejection.Breaking: TypeScript gets the same two errors.
MintTokenErrorCodeandTokenSourceErrorCodeare literal unions rather than aliases ofstring, both errors take{ code, message, serverCode }, and a malformedTokenSource.endpointURL now throws aTypeErrorrather than an SDK error — argument validation is not part of the error family, which is what the Python SDK already did.Breaking: Swift gets the same two errors, as structs replacing the
MintTokenErrorenum.MintTokenError.rejected(code:detail:),.transport(message:)and.invalidResponse(message:)are gone; construct or matchMintTokenError(code:message:serverCode:)with aMintTokenErrorCodeinstead, and expectTokenSourceErrorwhere aTokenSourcefetch used to raise a mint error.mintTokennow refuses a client built with a minted token or a token source before the request goes out, with codemissingApiKey, rather than letting the server answer 401 — andTokenSource.customrejects a fetcher returning an emptyjwtinstead of sending it as a bearer.// before — code could be the SDK's category or the server's slug if (err.code === 'auth_failed') reauthenticate(); // after — code is the SDK's closed set; the server's slug is serverCode if (err.code === 'request_rejected' && err.serverCode === 'auth_failed') reauthenticate(); -
The unused
ScreenShareOptionstype is removed. No API ever accepted it —startScreenShare()takes no options — so the only migration is deleting the import; annotate with your own shape if you referenced it. -
The deprecated
CosmoRealtimeProviderandCosmoRealtimeProviderPropsaliases are removed. ImportRealtimeProviderandRealtimeProviderPropsinstead — the same component and props type under the names the rename already established. -
A
TokenSource.customfetcher now resolves with the same shapemintTokenreturns — aMintedToken— in every SDK. Python no longer accepts a plain{jwt, expires_at}mapping (constructMintedToken, which still parses an RFC 3339expires_atstring), and TypeScript no longer accepts a stringexpiresAt(pass aDate; theFetchedTokentype is removed — annotate withMintedToken). Python's directTokenSource(...)construction now validates identically tocustominstead of bypassing it. Swift is unchanged.// before TokenSource.custom(async () => ({ jwt, expiresAt: '2026-09-14T00:00:00Z' })); // after — expiresAt is a Date; annotate with MintedToken TokenSource.custom(async (): Promise<MintedToken> => ({ jwt, expiresAt: new Date(expiry) })); -
AmbienceConfigand the agent'saudio.ambiencefield are removed from every SDK, The field never produced audible ambience on any session started through this API — it was accepted and validated, then dropped — so removing it changes no behavior. Deleteambiencefrom your agent'saudioblock; the rest of the block is unchanged. -
The five backend calls share an
ApiErrorbase —MintTokenError,TokenSourceError,VerifyError,UsageErrorandDialErrorall descend from it, so one catch covers any of them while catching a specific one still says which call it was.serverCodemoves to the base, because a rejection slug belongs to whichever backend answered rather than to the call that asked.VerifyError,UsageErrorandDialErrorgain closed code enums in place of a bare string:request_failed,request_rejected,invalid_response, plusinvalid_requeston dial and usage for a call the SDK refuses to make. Where a server slug was thecode, it is nowserverCodeandcodeisrequest_rejected. Swift gainsDialError, which it did not have, and itsUsageErrorandVerifyErrorbecome structs carryingcoderather than case-carrying enums. -
One
CredentialsErrorwith the closedCredentialsErrorCodereplaces six spellings of the same failure — TypeScript'sCredentialError(singular), Python'sCredentialsErrorplus itsNotFound,File,ExpiredandMismatchsubclasses, and Swift's case-carrying enum. The five resolution codes are the slugs the cross-SDK vectors already pinned;conflicting_credentials,api_key_in_token_slotandinsecure_base_urlcover the construction-time guards, which previously threw a bareError. -
The session's
errorevent anduseRealtimeError()now deliver the error itself instead of a summary of it. The value is theSessionStartErrororAudioUnavailableErrorthatstart()rejected with, or anErrorEventcarrying the server's owncodeandfatal— so a banner and acatchblock see the same object, and a server code is no longer collapsed into a client bucket.ErrorCodeis now the server's error enum, the same one Python and Swift publish, and the client-side union that held the name is gone.Code reading
error.codeanderror.messagekeeps working. Code matching the old client codes changes:auth_errorbecomesauth_failedorworkspace_forbidden,server_errorbecomes the server's real code, andmic_deniedand the other device failures arrive asAudioUnavailableErrorwith anAudioUnavailableErrorCode. Branch withinstanceof RealtimeErrorandnamefor the typed errors; the remaining case is the server's event.ErrorEventgainsfatal, always present —falsefor an error the session survives.A mid-session transport drop no longer latches on this axis — read
session.state(disconnectReason: 'transport_error') or thesession_endedevent for it — and a failed mic toggle reaches only the caller that awaited it. A start failure that the transport raised untyped is now wrapped inSessionStartErrorwithcode: 'join_failed', carrying the original as itscause. A non-fatal error no longer moves the transport tofailed.// before — every failure arrived as a summary with a client bucket session.on('error', (e) => { if (e.code === 'auth_error') signIn(); }); // after — the event delivers the error itself, or null on the clear session.on('error', (error) => { if (error === null) return hideBanner(); // healthy again — e.g. the next start if (error instanceof SessionStartError) { if (error.serverCode === 'auth_failed') signIn(); else showStartFailure(error.code); // busy, entitlement, config, … } else if (error instanceof AudioUnavailableError) { askForMicrophone(); } else { banner(error.code, error.fatal); // the server's ErrorEvent } }); -
Registering a hook that cannot work now throws
HookErrorin every SDK, with the closedHookErrorCode—malformed_matcher,invalid_hook,server_hook_not_allowed. These previously threw a bareError, so none was catchable asRealtimeError. -
A microphone that will not open now throws
AudioUnavailableError, whosecodesays which failure it was —mic_denied,mic_not_found,mic_in_use, oraudio_unavailable— where the browser's own exception was raised before, andMicStategains'in-use'to match. Code that switches exhaustively overMicState, or that read the browser exception off a failed start, needs the new member and the new type; Python and Swift raise the same error with the same codes. -
ScreenCaptureRequestno longer carrieswantsElements(wants_elementsin Python); the request is an empty envelope, and in Swift its initializer isinit(). Capture handlers should always collect elements — the server has always requested them, so nothing changes at runtime. A handler that read the flag can simply drop the check. -
Declaring
screen_locateon the websocket transport now refuses at session start withSessionStartError(code: "config",server_code: "screen_locate_unsupported") — the locator's capture payload travels as a byte stream, a channel the single-socket carrier does not have. Previously TypeScript silently skipped registration and Python and Swift captured the screen and then failed to deliver it; now the capture handler never runs. The Python background-tools refusal gains the matchingserver_code: "background_tools_unsupported". -
ErrorCode,SessionStatus,UsageStatusandCredentialKindnow accept a value the server added after your package shipped, instead of failing the payload that carried it — previously an unrecognized error code cost the whole error event, and an unrecognized status failed the usage request along with the counters you asked for. Swift switches over these enums need adefaultor@unknown defaultnow that they carry anunknown(String)case and are no longer@frozen; in Python both kinds are members of the enum, so usevalue in list(TheEnum)rather thanisinstanceto tell them apart.TranscriptRoleis unchanged.In Python a server-added value is an enum member like any other, so declared versus added is a list check:
-
The session now owns the coalesced transcript, and the do-it-yourself folding surface is removed. Read
session.transcript(oneTranscriptItemper turn, with a stableidand anisFinalflag) or subscribe totranscript_updated, which carries the full updated list and replays the current value on subscribe;useTranscript()now reads this state and no longer caps at 12 items. Removed:reduceTranscript,RealtimeTranscriptItem, thecore/transcript_foldandcore/transcript_reducermodules, andRealtimeProvider'smaxTranscriptLengthprop — rendersession.transcript(or passuseTranscript({ limit })) instead.sendTextnow lands the sent text in the transcript as its own closed user turn (an in-progress speech turn is unaffected) unlesstranscript: falseis passed. The rawtranscriptdelta events are unchanged and remain available.// before — fold the deltas yourself let items: RealtimeTranscriptItem[] = []; session.on('transcript', (delta) => { items = reduceTranscript(items, delta, 100); }); // after — the session holds the folded transcript session.on('transcript_updated', ({ items }) => render(items)); render(session.transcript); // same value, readable any time -
Every way a start can fail now raises one
SessionStartError, whose closedSessionStartErrorCodenames how far the attempt got —transport,invalid_response,join_failed,config,busy,entitlement,version_mismatch,voice_disabled,rejected,handshake_failed,ready_timeout. Switch oncodewhere you used to branch on a type or read an HTTP status, and read the server's own rejection slug fromserverCodebeside it. It replacesSessionBusyError,SessionEntitlementError,SessionConfigError,VersionMismatchErrorandSessionStartTransportError. In Python the base error'scode— previously open, carrying the server's own slug or a synthetichttp_<status>— closes to the enum, with the slug moving toserver_code.// before try { await agent.start(); } catch (e) { if (e instanceof SessionBusyError) retryIn(e.retryAfterSeconds); else if (e instanceof SessionEntitlementError) showUpgrade(); else throw e; } // after try { await agent.start(); } catch (e) { if (!(e instanceof SessionStartError)) throw e; if (e.code === 'busy') retryIn(e.retryAfterSeconds); else if (e.code === 'entitlement') showUpgrade(); else throw e; } -
SessionStartError.detailis aSessionStartRejectionin every SDK — the server's structured reason for refusing a start, which no SDK carried in full before. Each group of fields belongs to one server code:limit/activeforconcurrent_session_limit,granted_minutes/used_minutesforfree_minutes_exhausted,balance_cents/top_up_pathforinsufficient_credits,meter/included/used/reset_atforquota_exceeded, andprovider/allowed_providers/plan/upgrade_pathforprovider_not_entitled. A field the server adds that the SDK does not name is kept rather than dropped.RealtimeSessionStartDetailis renamed toSessionStartRejectionand gains the ten fields it never declared. -
Calling a session method the session cannot serve now throws
SessionStateErrorwith the closedSessionStateErrorCode—not_connected,already_started,audio_publish_already_active,video_publish_already_active,screen_share_unavailable,invalid_payload. It replacesNotReadyErrorandAudioPublishAlreadyActiveError. A send issued beforereadyand one issued after the session ended both reportnot_connected. -
The session state machine's value type has one name in every SDK —
SessionState. TypeScript'sSessionLifecycleStateand Python'sRealtimeSessionStateare renamed; fields, kinds, and behavior are unchanged, so the migration is the rename alone. TypeScript'skindfield also gains the namedSessionStateKindtype (the same name Python exports); the values are unchanged, so existing code is unaffected. -
agent.start()now resolves when the session is ready — the server's handshake has landed — instead of at transport join, so every session method works the moment the promise settles. The naiveawait start(); sendText(...)sequence is now correct as written, andwaitUntilReady()is no longer needed on the golden path (it remains, and resolves instantly after a resolved start). A session whose ready handshake never arrives within 40 seconds is torn down andstart()rejects withSessionStartErrorcodedready_timeout; a room that closes before ready rejects with it codedhandshake_failed, carrying the server's boot-failureerrorframe (code and message) when one preceded the close — where previously such failures surfaced only as anerrorevent after the fact. A pre-readyerrorframe on its own never rejectsstart(); the close that follows carries its detail.SessionStateErrorcodednot_connectedfrom a session method now means the session has ended or never started. Readiness is also published as room state (a participant attribute on the agent), so a client that joins after the agent came up — a mid-call observer, a reconnect — still observesready. -
ToolSchemaErroris nowToolDefinitionError, and it covers the whole declaration — a bad tool name and a missing or overlong description throw it too, where a bareErrorwas thrown before. It is now catchable asRealtimeErrorlike every other SDK error.codeis the closedToolDefinitionErrorCoderather than a string. -
A tool-call validation failure reports its issues as
ToolInputIssuein every SDK —path,code,constraint— where Python had raw dictionaries keyedloc/type/ctx, Swift nested the type inside the error, and TypeScript carriedpathas an array of segments.pathis now the dotted form (address.city,items[2].sku) everywhere, the same string theINVALID_INPUTmessage renders.TypeScript exports
ToolInputIssuefromcosmo-ai/tool: it is the typeToolInputValidationError.issuescarries, so a caller reading them has to be able to name it. -
AgentToolis now opaque — a tool is built by calling its constructor, and a hand-written object literal no longer type-checks intools. The wire discriminant and the per-tool shapes are internal, as they are in the Python and Swift SDKs. Migrate each literal to the constructor that builds it:{ kind: 'web_search' }becomeswebSearchTool()(likewiseexamineImageTool(),detectObjectsTool(),pointAtObjectTool(),endCallTool(),speakerLogTool(), andscreenLocateTool(capture)), and akind: 'client'spec becomesclientTool({ name, description, parameters, handler })— orbackgroundClientTool(...)for the background form — with a typedinputor rawparameters, exactly as before. -
A client tool carries the handler that runs it —
handleris now required on the specsclientToolandbackgroundClientToolbuild, so a hand-builtkind: 'client'literal without one no longer type-checks. A handler-less spec advertised a tool that failed on every invocation; attach the handler that runs the tool. A method the server invokes over RPC without advertising it to the model is unchanged — that issession.registerRpcMethod, the register-only complement. -
A client constructed with no credential and no
getAuthHeadersnow throwsCredentialError(code: "no_credential") at its first authenticated call, before any request is sent, instead of sending unauthenticated requests the server rejects with 401. On such a clientmintTokenalso throws this instead ofMintTokenError(missing_api_key), which remains the error for token- andgetAuthHeaders-credentialed clients. This matches what the Python and Swift constructors already do. PassapiKeyortoken, setCOSMO_API_KEY, sign in withcosmo login, or supplygetAuthHeaders. -
The session stream now yields the SDK's own event types instead of raw wire frames, so the type you can import is the type you receive. A consumer iterating
for await (const event of session)reads camelCase fields —event.toolCallIdwhere it wasevent.tool_call_id,event.isFinalwhere it wasevent.is_final, plusevent.sessionId,event.secondsRemainingandevent.updatedKeys— andReadyEvent,ToolCallEvent,UsageEventand the rest now annotate a stream item as well as anon()payload.event.typeis the SDK's own name for the event, not the wire's:'tool_call'rather than'tool-call','model_text'rather than'model-text','usage'and'session_state'rather than'cosmo.usage'and'cosmo.session-state'. Where an event also has a callback, that is the nameon()takes, so one vocabulary names it on either surface; thebot_*anduser_*_speakingmarkers andtool_invocationreach the stream only. Callback payloads are unchanged.Nine events that only ever reached the stream are now published types:
BotLlmStartedEvent,BotLlmStoppedEvent,BotStartedSpeakingEvent,BotStoppedSpeakingEvent,BotTtsStartedEvent,BotTtsStoppedEvent,UserStartedSpeakingEvent,UserStoppedSpeakingEventandToolInvocationEvent, alongsideToolInvocationOrigin. Optional wire fields read as settled values rather thanundefined: a missingsummaryisnull, missing counters are0, andargs,state,updatedKeysandrejectedToolsare empty rather than absent.// before — the stream yielded raw wire frames for await (const event of session) { if (event.type === 'tool-call') handle(event.tool_call_id); } // after — the stream yields the SDK's own event types, camelCased for await (const event of session) { if (event.type === 'tool_call') handle(event.toolCallId); } -
cosmo-ai/tool/screenstops exporting theScreenLocateToolmember type and the capture plumbing (SCREEN_CAPTURE_RPC_METHOD,screenCaptureRpc,screenCapturePayload,ScreenCaptureCache). The member type was the wire model behindscreenLocateTool(capture)— annotate tool values withAgentTool— and the plumbing was the wiring behind it, never consumer API; the constructor and the capture-handler contract are unchanged. -
The React bindings are no longer re-exported from the package root — import them from
cosmo-ai/react, which carries the whole surface.import { RealtimeProvider, useTranscript } from 'cosmo-ai'becomesimport { RealtimeProvider, useTranscript } from 'cosmo-ai/react'; nothing else moves, and every name keeps its spelling. In exchangereactandreact-dombecome optional peer dependencies, so a headless Node app no longer installs React to use the SDK, andcosmo-ai/serveris now a narrower surface by choice rather than a workaround for the root pulling React into thereact-servergraph.