From c85533a00ccec1ea0b16c980cf17ededffc51fb7 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 5 Nov 2026 19:47:27 -0800 Subject: [PATCH] chore(contracts): sync the worker's steering v2 contract Additive copies of the worker's SteerV2Request, SteerV2Response and steering_applied frame shape, generated from simstudioai/mothership#929. Nothing in Sim uses them yet; Sim's side of a paired contract merges first. --- .../generated/mothership-stream-v1.ts | 20 ++++++ apps/sim/lib/mothership/generated/protocol.ts | 67 +++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/apps/sim/lib/mothership/generated/mothership-stream-v1.ts b/apps/sim/lib/mothership/generated/mothership-stream-v1.ts index 69407a78cf1..2889f7cc1c1 100644 --- a/apps/sim/lib/mothership/generated/mothership-stream-v1.ts +++ b/apps/sim/lib/mothership/generated/mothership-stream-v1.ts @@ -23,6 +23,7 @@ export type MothershipStreamV1EventEnvelope = | MothershipStreamV1RunResumedEventEnvelope | MothershipStreamV1CompactionStartEventEnvelope | MothershipStreamV1CompactionDoneEventEnvelope + | MothershipStreamV1SteeringAppliedEventEnvelope | MothershipStreamV1TaskArmedEventEnvelope | MothershipStreamV1TaskDeliveredEventEnvelope | MothershipStreamV1ErrorEventEnvelope @@ -371,6 +372,25 @@ export interface MothershipStreamV1CompactionDonePayload { export interface MothershipStreamV1CompactionDoneData { summary_chars: number } +export interface MothershipStreamV1SteeringAppliedEventEnvelope { + payload: MothershipStreamV1SteeringAppliedPayload + scope?: MothershipStreamV1StreamScope + seq: number + stream: MothershipStreamV1StreamRef + trace?: MothershipStreamV1Trace + ts: string + type: 'run' + v: 1 +} +export interface MothershipStreamV1SteeringAppliedPayload { + content?: string + eventSeq?: number + kind: 'steering_applied' + messageId?: string + mode?: 'deferred' | 'interrupt' + steeringId: string + textOffset?: number +} export interface MothershipStreamV1TaskArmedEventEnvelope { payload: MothershipStreamV1TaskArmedPayload scope?: MothershipStreamV1StreamScope diff --git a/apps/sim/lib/mothership/generated/protocol.ts b/apps/sim/lib/mothership/generated/protocol.ts index 64b473165f1..b4367cb8b5f 100644 --- a/apps/sim/lib/mothership/generated/protocol.ts +++ b/apps/sim/lib/mothership/generated/protocol.ts @@ -511,6 +511,73 @@ export interface SteerRequest { content: string; } +/** + * The images one run takes in steers over its whole life. With a turn's own five they stay + * under the twenty image and document blocks a model request holds. + */ +export const MAX_RUN_STEERED_IMAGES = 10; + +/** The most attachments and mentions one steer carries: Sim's MAX_CHAT_CONTEXTS for a turn. */ +export const MAX_STEER_CONTEXTS = 100; + +/** + * POST /api/streams/steer/v2: steer the live run `messageId` with one immutable input, answered + * with its durable disposition, which a retry of the same body reads back. Only `not_applied` + * (or a 422) lets the caller send the input as a new turn, with the steeringId as its + * messageId; a turn for an input a run applied is refused. Refusals keep nothing: + * - 400: an invalid body, or images outside an organization chat (`steering_scope`). + * - 404 `steering_target_missing`: no such run (or not admitted yet). + * - 409 `steering_conflict`: a different input under this steeringId. + * - 422 `steering_image_limit`: past MAX_RUN_STEERED_IMAGES, applied images included. Final + * for the run: send the input as the next turn once the run ends. + * - 429 `steering_capacity`: the pending queue is full; retryable once the run applies it. + */ +export const SteerV2Request = z.strictObject({ + messageId: z.uuid(), + steeringId: z.uuid(), + content: z.string().min(1).max(1_048_576), + /** Attachments and @-mentions, rendered into the steer the way a turn renders its own. */ + context: ChatPayloadSchema.shape.context.unwrap().max(MAX_STEER_CONTEXTS).default([]), + /** Only an organization chat accepts images, as for a turn (400 otherwise). */ + assistantImages: ChatPayloadSchema.shape.assistantImages, +}); +export type SteerV2Request = z.infer; + +/** + * - `queued`: accepted; the run applies it at its next model step, or ends first and then + * reports `not_applied`. + * - `applied`: logged into the run's model input, which its next model request carries (as + * v1's frame means). `eventSeq` is its steering_input event in the chat + * log and `textOffset` the main answer's length when it applied (UTF-16 code units), the + * same values the `steering_applied` frame carries. + * - `not_applied`: the run ended, or was stopped, before applying it, and never will. A run + * that would apply it only by exceeding its usage limit, or one whose input the request + * supplies (an execution, a structured response), ends instead and reports this. + * + * A steeringId belongs to the run of the chat that first accepted it: a retry naming another + * run of the chat reads that run's disposition. + */ +export const SteerV2Response = z.discriminatedUnion("status", [ + z.strictObject({ steeringId: z.string(), status: z.literal("queued") }), + z.strictObject({ + steeringId: z.string(), + status: z.literal("applied"), + eventSeq: z.number().int().positive(), + textOffset: z.number().int().nonnegative(), + }), + z.strictObject({ steeringId: z.string(), status: z.literal("not_applied") }), +]); +export type SteerV2Response = z.infer; + +/** The `run`/`steering_applied` frame: a steer reached the run's model input. */ +export interface StreamSteeringApplied { + kind: "steering_applied"; + steeringId: string; + /** As in SteerV2Response's `applied`; absent only on frames from workers without steer v2. */ + eventSeq?: number | undefined; + textOffset?: number | undefined; +} + /** POST /api/chats/fork — copy a selected conversation snapshot, never live execution. */ export const ForkChatRequest = z .strictObject({