diff --git a/packages/client/src/effect/api/api.ts b/packages/client/src/effect/api/api.ts index 76f243c3dc..341c7e2f49 100644 --- a/packages/client/src/effect/api/api.ts +++ b/packages/client/src/effect/api/api.ts @@ -74,192 +74,198 @@ export type Endpoint4_3Input = { readonly sessionID: Endpoint4_3Request["params" export type Endpoint4_3Output = EffectValue>["data"] export type SessionGetOperation = (input: Endpoint4_3Input) => Effect.Effect -type Endpoint4_4Request = Parameters[0] -export type Endpoint4_4Input = { - readonly sessionID: Endpoint4_4Request["params"]["sessionID"] - readonly messageID?: Endpoint4_4Request["payload"]["messageID"] -} -export type Endpoint4_4Output = EffectValue>["data"] -export type SessionForkOperation = (input: Endpoint4_4Input) => Effect.Effect +type Endpoint4_4Request = Parameters[0] +export type Endpoint4_4Input = { readonly sessionID: Endpoint4_4Request["params"]["sessionID"] } +export type Endpoint4_4Output = EffectValue> +export type SessionRemoveOperation = (input: Endpoint4_4Input) => Effect.Effect -type Endpoint4_5Request = Parameters[0] +type Endpoint4_5Request = Parameters[0] export type Endpoint4_5Input = { readonly sessionID: Endpoint4_5Request["params"]["sessionID"] - readonly agent: Endpoint4_5Request["payload"]["agent"] + readonly messageID?: Endpoint4_5Request["payload"]["messageID"] } -export type Endpoint4_5Output = EffectValue> -export type SessionSwitchAgentOperation = (input: Endpoint4_5Input) => Effect.Effect +export type Endpoint4_5Output = EffectValue>["data"] +export type SessionForkOperation = (input: Endpoint4_5Input) => Effect.Effect -type Endpoint4_6Request = Parameters[0] +type Endpoint4_6Request = Parameters[0] export type Endpoint4_6Input = { readonly sessionID: Endpoint4_6Request["params"]["sessionID"] - readonly model: Endpoint4_6Request["payload"]["model"] + readonly agent: Endpoint4_6Request["payload"]["agent"] } -export type Endpoint4_6Output = EffectValue> -export type SessionSwitchModelOperation = (input: Endpoint4_6Input) => Effect.Effect +export type Endpoint4_6Output = EffectValue> +export type SessionSwitchAgentOperation = (input: Endpoint4_6Input) => Effect.Effect -type Endpoint4_7Request = Parameters[0] +type Endpoint4_7Request = Parameters[0] export type Endpoint4_7Input = { readonly sessionID: Endpoint4_7Request["params"]["sessionID"] - readonly title: Endpoint4_7Request["payload"]["title"] + readonly model: Endpoint4_7Request["payload"]["model"] } -export type Endpoint4_7Output = EffectValue> -export type SessionRenameOperation = (input: Endpoint4_7Input) => Effect.Effect +export type Endpoint4_7Output = EffectValue> +export type SessionSwitchModelOperation = (input: Endpoint4_7Input) => Effect.Effect -type Endpoint4_8Request = Parameters[0] +type Endpoint4_8Request = Parameters[0] export type Endpoint4_8Input = { readonly sessionID: Endpoint4_8Request["params"]["sessionID"] - readonly id?: Endpoint4_8Request["payload"]["id"] - readonly prompt: Endpoint4_8Request["payload"]["prompt"] - readonly delivery?: Endpoint4_8Request["payload"]["delivery"] - readonly resume?: Endpoint4_8Request["payload"]["resume"] + readonly title: Endpoint4_8Request["payload"]["title"] } -export type Endpoint4_8Output = EffectValue>["data"] -export type SessionPromptOperation = (input: Endpoint4_8Input) => Effect.Effect +export type Endpoint4_8Output = EffectValue> +export type SessionRenameOperation = (input: Endpoint4_8Input) => Effect.Effect -type Endpoint4_9Request = Parameters[0] +type Endpoint4_9Request = Parameters[0] export type Endpoint4_9Input = { readonly sessionID: Endpoint4_9Request["params"]["sessionID"] readonly id?: Endpoint4_9Request["payload"]["id"] - readonly command: Endpoint4_9Request["payload"]["command"] - readonly arguments?: Endpoint4_9Request["payload"]["arguments"] - readonly agent?: Endpoint4_9Request["payload"]["agent"] - readonly model?: Endpoint4_9Request["payload"]["model"] - readonly files?: Endpoint4_9Request["payload"]["files"] - readonly agents?: Endpoint4_9Request["payload"]["agents"] + readonly prompt: Endpoint4_9Request["payload"]["prompt"] readonly delivery?: Endpoint4_9Request["payload"]["delivery"] readonly resume?: Endpoint4_9Request["payload"]["resume"] } -export type Endpoint4_9Output = EffectValue>["data"] -export type SessionCommandOperation = (input: Endpoint4_9Input) => Effect.Effect +export type Endpoint4_9Output = EffectValue>["data"] +export type SessionPromptOperation = (input: Endpoint4_9Input) => Effect.Effect -type Endpoint4_10Request = Parameters[0] +type Endpoint4_10Request = Parameters[0] export type Endpoint4_10Input = { readonly sessionID: Endpoint4_10Request["params"]["sessionID"] readonly id?: Endpoint4_10Request["payload"]["id"] - readonly skill: Endpoint4_10Request["payload"]["skill"] + readonly command: Endpoint4_10Request["payload"]["command"] + readonly arguments?: Endpoint4_10Request["payload"]["arguments"] + readonly agent?: Endpoint4_10Request["payload"]["agent"] + readonly model?: Endpoint4_10Request["payload"]["model"] + readonly files?: Endpoint4_10Request["payload"]["files"] + readonly agents?: Endpoint4_10Request["payload"]["agents"] + readonly delivery?: Endpoint4_10Request["payload"]["delivery"] readonly resume?: Endpoint4_10Request["payload"]["resume"] } -export type Endpoint4_10Output = EffectValue> -export type SessionSkillOperation = (input: Endpoint4_10Input) => Effect.Effect +export type Endpoint4_10Output = EffectValue>["data"] +export type SessionCommandOperation = (input: Endpoint4_10Input) => Effect.Effect -type Endpoint4_11Request = Parameters[0] +type Endpoint4_11Request = Parameters[0] export type Endpoint4_11Input = { readonly sessionID: Endpoint4_11Request["params"]["sessionID"] - readonly text: Endpoint4_11Request["payload"]["text"] - readonly description?: Endpoint4_11Request["payload"]["description"] - readonly metadata?: Endpoint4_11Request["payload"]["metadata"] + readonly id?: Endpoint4_11Request["payload"]["id"] + readonly skill: Endpoint4_11Request["payload"]["skill"] + readonly resume?: Endpoint4_11Request["payload"]["resume"] } -export type Endpoint4_11Output = EffectValue> -export type SessionSyntheticOperation = (input: Endpoint4_11Input) => Effect.Effect +export type Endpoint4_11Output = EffectValue> +export type SessionSkillOperation = (input: Endpoint4_11Input) => Effect.Effect -type Endpoint4_12Request = Parameters[0] +type Endpoint4_12Request = Parameters[0] export type Endpoint4_12Input = { readonly sessionID: Endpoint4_12Request["params"]["sessionID"] - readonly id?: Endpoint4_12Request["payload"]["id"] - readonly command: Endpoint4_12Request["payload"]["command"] + readonly text: Endpoint4_12Request["payload"]["text"] + readonly description?: Endpoint4_12Request["payload"]["description"] + readonly metadata?: Endpoint4_12Request["payload"]["metadata"] } -export type Endpoint4_12Output = EffectValue> -export type SessionShellOperation = (input: Endpoint4_12Input) => Effect.Effect +export type Endpoint4_12Output = EffectValue> +export type SessionSyntheticOperation = (input: Endpoint4_12Input) => Effect.Effect -type Endpoint4_13Request = Parameters[0] -export type Endpoint4_13Input = { readonly sessionID: Endpoint4_13Request["params"]["sessionID"] } -export type Endpoint4_13Output = EffectValue> -export type SessionCompactOperation = (input: Endpoint4_13Input) => Effect.Effect +type Endpoint4_13Request = Parameters[0] +export type Endpoint4_13Input = { + readonly sessionID: Endpoint4_13Request["params"]["sessionID"] + readonly id?: Endpoint4_13Request["payload"]["id"] + readonly command: Endpoint4_13Request["payload"]["command"] +} +export type Endpoint4_13Output = EffectValue> +export type SessionShellOperation = (input: Endpoint4_13Input) => Effect.Effect -type Endpoint4_14Request = Parameters[0] +type Endpoint4_14Request = Parameters[0] export type Endpoint4_14Input = { readonly sessionID: Endpoint4_14Request["params"]["sessionID"] } -export type Endpoint4_14Output = EffectValue> -export type SessionWaitOperation = (input: Endpoint4_14Input) => Effect.Effect +export type Endpoint4_14Output = EffectValue> +export type SessionCompactOperation = (input: Endpoint4_14Input) => Effect.Effect -type Endpoint4_15Request = Parameters[0] -export type Endpoint4_15Input = { - readonly sessionID: Endpoint4_15Request["params"]["sessionID"] - readonly messageID: Endpoint4_15Request["payload"]["messageID"] - readonly files?: Endpoint4_15Request["payload"]["files"] +type Endpoint4_15Request = Parameters[0] +export type Endpoint4_15Input = { readonly sessionID: Endpoint4_15Request["params"]["sessionID"] } +export type Endpoint4_15Output = EffectValue> +export type SessionWaitOperation = (input: Endpoint4_15Input) => Effect.Effect + +type Endpoint4_16Request = Parameters[0] +export type Endpoint4_16Input = { + readonly sessionID: Endpoint4_16Request["params"]["sessionID"] + readonly messageID: Endpoint4_16Request["payload"]["messageID"] + readonly files?: Endpoint4_16Request["payload"]["files"] } -export type Endpoint4_15Output = EffectValue>["data"] -export type SessionRevertStageOperation = (input: Endpoint4_15Input) => Effect.Effect +export type Endpoint4_16Output = EffectValue>["data"] +export type SessionRevertStageOperation = (input: Endpoint4_16Input) => Effect.Effect -type Endpoint4_16Request = Parameters[0] -export type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] } -export type Endpoint4_16Output = EffectValue> -export type SessionRevertClearOperation = (input: Endpoint4_16Input) => Effect.Effect - -type Endpoint4_17Request = Parameters[0] +type Endpoint4_17Request = Parameters[0] export type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] } -export type Endpoint4_17Output = EffectValue> -export type SessionRevertCommitOperation = (input: Endpoint4_17Input) => Effect.Effect +export type Endpoint4_17Output = EffectValue> +export type SessionRevertClearOperation = (input: Endpoint4_17Input) => Effect.Effect -type Endpoint4_18Request = Parameters[0] +type Endpoint4_18Request = Parameters[0] export type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] } -export type Endpoint4_18Output = EffectValue>["data"] -export type SessionContextOperation = (input: Endpoint4_18Input) => Effect.Effect +export type Endpoint4_18Output = EffectValue> +export type SessionRevertCommitOperation = (input: Endpoint4_18Input) => Effect.Effect -type Endpoint4_19Request = Parameters[0] +type Endpoint4_19Request = Parameters[0] export type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] } -export type Endpoint4_19Output = EffectValue< +export type Endpoint4_19Output = EffectValue>["data"] +export type SessionContextOperation = (input: Endpoint4_19Input) => Effect.Effect + +type Endpoint4_20Request = Parameters[0] +export type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] } +export type Endpoint4_20Output = EffectValue< ReturnType >["data"] export type SessionInstructionsEntryListOperation = ( - input: Endpoint4_19Input, -) => Effect.Effect - -type Endpoint4_20Request = Parameters[0] -export type Endpoint4_20Input = { - readonly sessionID: Endpoint4_20Request["params"]["sessionID"] - readonly key: Endpoint4_20Request["params"]["key"] - readonly value: Endpoint4_20Request["payload"]["value"] -} -export type Endpoint4_20Output = EffectValue> -export type SessionInstructionsEntryPutOperation = ( input: Endpoint4_20Input, ) => Effect.Effect -type Endpoint4_21Request = Parameters[0] +type Endpoint4_21Request = Parameters[0] export type Endpoint4_21Input = { readonly sessionID: Endpoint4_21Request["params"]["sessionID"] readonly key: Endpoint4_21Request["params"]["key"] + readonly value: Endpoint4_21Request["payload"]["value"] } -export type Endpoint4_21Output = EffectValue< - ReturnType -> -export type SessionInstructionsEntryRemoveOperation = ( +export type Endpoint4_21Output = EffectValue> +export type SessionInstructionsEntryPutOperation = ( input: Endpoint4_21Input, ) => Effect.Effect -type Endpoint4_22Request = Parameters[0] +type Endpoint4_22Request = Parameters[0] export type Endpoint4_22Input = { readonly sessionID: Endpoint4_22Request["params"]["sessionID"] - readonly after?: Endpoint4_22Request["query"]["after"] - readonly follow?: Endpoint4_22Request["query"]["follow"] + readonly key: Endpoint4_22Request["params"]["key"] } -export type Endpoint4_22Output = StreamValue>> -export type SessionLogOperation = (input: Endpoint4_22Input) => Stream.Stream +export type Endpoint4_22Output = EffectValue< + ReturnType +> +export type SessionInstructionsEntryRemoveOperation = ( + input: Endpoint4_22Input, +) => Effect.Effect -type Endpoint4_23Request = Parameters[0] -export type Endpoint4_23Input = { readonly sessionID: Endpoint4_23Request["params"]["sessionID"] } -export type Endpoint4_23Output = EffectValue> -export type SessionInterruptOperation = (input: Endpoint4_23Input) => Effect.Effect +type Endpoint4_23Request = Parameters[0] +export type Endpoint4_23Input = { + readonly sessionID: Endpoint4_23Request["params"]["sessionID"] + readonly after?: Endpoint4_23Request["query"]["after"] + readonly follow?: Endpoint4_23Request["query"]["follow"] +} +export type Endpoint4_23Output = StreamValue>> +export type SessionLogOperation = (input: Endpoint4_23Input) => Stream.Stream -type Endpoint4_24Request = Parameters[0] +type Endpoint4_24Request = Parameters[0] export type Endpoint4_24Input = { readonly sessionID: Endpoint4_24Request["params"]["sessionID"] } -export type Endpoint4_24Output = EffectValue> -export type SessionBackgroundOperation = (input: Endpoint4_24Input) => Effect.Effect +export type Endpoint4_24Output = EffectValue> +export type SessionInterruptOperation = (input: Endpoint4_24Input) => Effect.Effect -type Endpoint4_25Request = Parameters[0] -export type Endpoint4_25Input = { - readonly sessionID: Endpoint4_25Request["params"]["sessionID"] - readonly messageID: Endpoint4_25Request["params"]["messageID"] +type Endpoint4_25Request = Parameters[0] +export type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] } +export type Endpoint4_25Output = EffectValue> +export type SessionBackgroundOperation = (input: Endpoint4_25Input) => Effect.Effect + +type Endpoint4_26Request = Parameters[0] +export type Endpoint4_26Input = { + readonly sessionID: Endpoint4_26Request["params"]["sessionID"] + readonly messageID: Endpoint4_26Request["params"]["messageID"] } -export type Endpoint4_25Output = EffectValue>["data"] -export type SessionMessageOperation = (input: Endpoint4_25Input) => Effect.Effect +export type Endpoint4_26Output = EffectValue>["data"] +export type SessionMessageOperation = (input: Endpoint4_26Input) => Effect.Effect export interface SessionApi { readonly list: SessionListOperation readonly create: SessionCreateOperation readonly active: SessionActiveOperation readonly get: SessionGetOperation + readonly remove: SessionRemoveOperation readonly fork: SessionForkOperation readonly switchAgent: SessionSwitchAgentOperation readonly switchModel: SessionSwitchModelOperation diff --git a/packages/client/src/effect/generated/client.ts b/packages/client/src/effect/generated/client.ts index 514f70a923..6acad68663 100644 --- a/packages/client/src/effect/generated/client.ts +++ b/packages/client/src/effect/generated/client.ts @@ -95,56 +95,61 @@ const Endpoint4_3 = (raw: RawClient["server.session"]) => (input: Endpoint4_3Inp Effect.map((value) => value.data), ) -type Endpoint4_4Request = Parameters[0] -type Endpoint4_4Input = { - readonly sessionID: Endpoint4_4Request["params"]["sessionID"] - readonly messageID?: Endpoint4_4Request["payload"]["messageID"] -} +type Endpoint4_4Request = Parameters[0] +type Endpoint4_4Input = { readonly sessionID: Endpoint4_4Request["params"]["sessionID"] } const Endpoint4_4 = (raw: RawClient["server.session"]) => (input: Endpoint4_4Input) => + raw["session.remove"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) + +type Endpoint4_5Request = Parameters[0] +type Endpoint4_5Input = { + readonly sessionID: Endpoint4_5Request["params"]["sessionID"] + readonly messageID?: Endpoint4_5Request["payload"]["messageID"] +} +const Endpoint4_5 = (raw: RawClient["server.session"]) => (input: Endpoint4_5Input) => raw["session.fork"]({ params: { sessionID: input["sessionID"] }, payload: { messageID: input["messageID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint4_5Request = Parameters[0] -type Endpoint4_5Input = { - readonly sessionID: Endpoint4_5Request["params"]["sessionID"] - readonly agent: Endpoint4_5Request["payload"]["agent"] +type Endpoint4_6Request = Parameters[0] +type Endpoint4_6Input = { + readonly sessionID: Endpoint4_6Request["params"]["sessionID"] + readonly agent: Endpoint4_6Request["payload"]["agent"] } -const Endpoint4_5 = (raw: RawClient["server.session"]) => (input: Endpoint4_5Input) => +const Endpoint4_6 = (raw: RawClient["server.session"]) => (input: Endpoint4_6Input) => raw["session.switchAgent"]({ params: { sessionID: input["sessionID"] }, payload: { agent: input["agent"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint4_6Request = Parameters[0] -type Endpoint4_6Input = { - readonly sessionID: Endpoint4_6Request["params"]["sessionID"] - readonly model: Endpoint4_6Request["payload"]["model"] +type Endpoint4_7Request = Parameters[0] +type Endpoint4_7Input = { + readonly sessionID: Endpoint4_7Request["params"]["sessionID"] + readonly model: Endpoint4_7Request["payload"]["model"] } -const Endpoint4_6 = (raw: RawClient["server.session"]) => (input: Endpoint4_6Input) => +const Endpoint4_7 = (raw: RawClient["server.session"]) => (input: Endpoint4_7Input) => raw["session.switchModel"]({ params: { sessionID: input["sessionID"] }, payload: { model: input["model"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint4_7Request = Parameters[0] -type Endpoint4_7Input = { - readonly sessionID: Endpoint4_7Request["params"]["sessionID"] - readonly title: Endpoint4_7Request["payload"]["title"] +type Endpoint4_8Request = Parameters[0] +type Endpoint4_8Input = { + readonly sessionID: Endpoint4_8Request["params"]["sessionID"] + readonly title: Endpoint4_8Request["payload"]["title"] } -const Endpoint4_7 = (raw: RawClient["server.session"]) => (input: Endpoint4_7Input) => +const Endpoint4_8 = (raw: RawClient["server.session"]) => (input: Endpoint4_8Input) => raw["session.rename"]({ params: { sessionID: input["sessionID"] }, payload: { title: input["title"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint4_8Request = Parameters[0] -type Endpoint4_8Input = { - readonly sessionID: Endpoint4_8Request["params"]["sessionID"] - readonly id?: Endpoint4_8Request["payload"]["id"] - readonly prompt: Endpoint4_8Request["payload"]["prompt"] - readonly delivery?: Endpoint4_8Request["payload"]["delivery"] - readonly resume?: Endpoint4_8Request["payload"]["resume"] +type Endpoint4_9Request = Parameters[0] +type Endpoint4_9Input = { + readonly sessionID: Endpoint4_9Request["params"]["sessionID"] + readonly id?: Endpoint4_9Request["payload"]["id"] + readonly prompt: Endpoint4_9Request["payload"]["prompt"] + readonly delivery?: Endpoint4_9Request["payload"]["delivery"] + readonly resume?: Endpoint4_9Request["payload"]["resume"] } -const Endpoint4_8 = (raw: RawClient["server.session"]) => (input: Endpoint4_8Input) => +const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Input) => raw["session.prompt"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"], prompt: input["prompt"], delivery: input["delivery"], resume: input["resume"] }, @@ -153,20 +158,20 @@ const Endpoint4_8 = (raw: RawClient["server.session"]) => (input: Endpoint4_8Inp Effect.map((value) => value.data), ) -type Endpoint4_9Request = Parameters[0] -type Endpoint4_9Input = { - readonly sessionID: Endpoint4_9Request["params"]["sessionID"] - readonly id?: Endpoint4_9Request["payload"]["id"] - readonly command: Endpoint4_9Request["payload"]["command"] - readonly arguments?: Endpoint4_9Request["payload"]["arguments"] - readonly agent?: Endpoint4_9Request["payload"]["agent"] - readonly model?: Endpoint4_9Request["payload"]["model"] - readonly files?: Endpoint4_9Request["payload"]["files"] - readonly agents?: Endpoint4_9Request["payload"]["agents"] - readonly delivery?: Endpoint4_9Request["payload"]["delivery"] - readonly resume?: Endpoint4_9Request["payload"]["resume"] +type Endpoint4_10Request = Parameters[0] +type Endpoint4_10Input = { + readonly sessionID: Endpoint4_10Request["params"]["sessionID"] + readonly id?: Endpoint4_10Request["payload"]["id"] + readonly command: Endpoint4_10Request["payload"]["command"] + readonly arguments?: Endpoint4_10Request["payload"]["arguments"] + readonly agent?: Endpoint4_10Request["payload"]["agent"] + readonly model?: Endpoint4_10Request["payload"]["model"] + readonly files?: Endpoint4_10Request["payload"]["files"] + readonly agents?: Endpoint4_10Request["payload"]["agents"] + readonly delivery?: Endpoint4_10Request["payload"]["delivery"] + readonly resume?: Endpoint4_10Request["payload"]["resume"] } -const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Input) => +const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10Input) => raw["session.command"]({ params: { sessionID: input["sessionID"] }, payload: { @@ -185,61 +190,61 @@ const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Inp Effect.map((value) => value.data), ) -type Endpoint4_10Request = Parameters[0] -type Endpoint4_10Input = { - readonly sessionID: Endpoint4_10Request["params"]["sessionID"] - readonly id?: Endpoint4_10Request["payload"]["id"] - readonly skill: Endpoint4_10Request["payload"]["skill"] - readonly resume?: Endpoint4_10Request["payload"]["resume"] +type Endpoint4_11Request = Parameters[0] +type Endpoint4_11Input = { + readonly sessionID: Endpoint4_11Request["params"]["sessionID"] + readonly id?: Endpoint4_11Request["payload"]["id"] + readonly skill: Endpoint4_11Request["payload"]["skill"] + readonly resume?: Endpoint4_11Request["payload"]["resume"] } -const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10Input) => +const Endpoint4_11 = (raw: RawClient["server.session"]) => (input: Endpoint4_11Input) => raw["session.skill"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"], skill: input["skill"], resume: input["resume"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint4_11Request = Parameters[0] -type Endpoint4_11Input = { - readonly sessionID: Endpoint4_11Request["params"]["sessionID"] - readonly text: Endpoint4_11Request["payload"]["text"] - readonly description?: Endpoint4_11Request["payload"]["description"] - readonly metadata?: Endpoint4_11Request["payload"]["metadata"] +type Endpoint4_12Request = Parameters[0] +type Endpoint4_12Input = { + readonly sessionID: Endpoint4_12Request["params"]["sessionID"] + readonly text: Endpoint4_12Request["payload"]["text"] + readonly description?: Endpoint4_12Request["payload"]["description"] + readonly metadata?: Endpoint4_12Request["payload"]["metadata"] } -const Endpoint4_11 = (raw: RawClient["server.session"]) => (input: Endpoint4_11Input) => +const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12Input) => raw["session.synthetic"]({ params: { sessionID: input["sessionID"] }, payload: { text: input["text"], description: input["description"], metadata: input["metadata"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint4_12Request = Parameters[0] -type Endpoint4_12Input = { - readonly sessionID: Endpoint4_12Request["params"]["sessionID"] - readonly id?: Endpoint4_12Request["payload"]["id"] - readonly command: Endpoint4_12Request["payload"]["command"] +type Endpoint4_13Request = Parameters[0] +type Endpoint4_13Input = { + readonly sessionID: Endpoint4_13Request["params"]["sessionID"] + readonly id?: Endpoint4_13Request["payload"]["id"] + readonly command: Endpoint4_13Request["payload"]["command"] } -const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12Input) => +const Endpoint4_13 = (raw: RawClient["server.session"]) => (input: Endpoint4_13Input) => raw["session.shell"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"], command: input["command"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint4_13Request = Parameters[0] -type Endpoint4_13Input = { readonly sessionID: Endpoint4_13Request["params"]["sessionID"] } -const Endpoint4_13 = (raw: RawClient["server.session"]) => (input: Endpoint4_13Input) => - raw["session.compact"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) - -type Endpoint4_14Request = Parameters[0] +type Endpoint4_14Request = Parameters[0] type Endpoint4_14Input = { readonly sessionID: Endpoint4_14Request["params"]["sessionID"] } const Endpoint4_14 = (raw: RawClient["server.session"]) => (input: Endpoint4_14Input) => + raw["session.compact"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) + +type Endpoint4_15Request = Parameters[0] +type Endpoint4_15Input = { readonly sessionID: Endpoint4_15Request["params"]["sessionID"] } +const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15Input) => raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint4_15Request = Parameters[0] -type Endpoint4_15Input = { - readonly sessionID: Endpoint4_15Request["params"]["sessionID"] - readonly messageID: Endpoint4_15Request["payload"]["messageID"] - readonly files?: Endpoint4_15Request["payload"]["files"] +type Endpoint4_16Request = Parameters[0] +type Endpoint4_16Input = { + readonly sessionID: Endpoint4_16Request["params"]["sessionID"] + readonly messageID: Endpoint4_16Request["payload"]["messageID"] + readonly files?: Endpoint4_16Request["payload"]["files"] } -const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15Input) => +const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16Input) => raw["session.revert.stage"]({ params: { sessionID: input["sessionID"] }, payload: { messageID: input["messageID"], files: input["files"] }, @@ -248,61 +253,61 @@ const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15I Effect.map((value) => value.data), ) -type Endpoint4_16Request = Parameters[0] -type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] } -const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16Input) => - raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) - -type Endpoint4_17Request = Parameters[0] +type Endpoint4_17Request = Parameters[0] type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] } const Endpoint4_17 = (raw: RawClient["server.session"]) => (input: Endpoint4_17Input) => - raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) + raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint4_18Request = Parameters[0] +type Endpoint4_18Request = Parameters[0] type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] } const Endpoint4_18 = (raw: RawClient["server.session"]) => (input: Endpoint4_18Input) => + raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) + +type Endpoint4_19Request = Parameters[0] +type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] } +const Endpoint4_19 = (raw: RawClient["server.session"]) => (input: Endpoint4_19Input) => raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint4_19Request = Parameters[0] -type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] } -const Endpoint4_19 = (raw: RawClient["server.session"]) => (input: Endpoint4_19Input) => +type Endpoint4_20Request = Parameters[0] +type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] } +const Endpoint4_20 = (raw: RawClient["server.session"]) => (input: Endpoint4_20Input) => raw["session.instructions.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint4_20Request = Parameters[0] -type Endpoint4_20Input = { - readonly sessionID: Endpoint4_20Request["params"]["sessionID"] - readonly key: Endpoint4_20Request["params"]["key"] - readonly value: Endpoint4_20Request["payload"]["value"] +type Endpoint4_21Request = Parameters[0] +type Endpoint4_21Input = { + readonly sessionID: Endpoint4_21Request["params"]["sessionID"] + readonly key: Endpoint4_21Request["params"]["key"] + readonly value: Endpoint4_21Request["payload"]["value"] } -const Endpoint4_20 = (raw: RawClient["server.session"]) => (input: Endpoint4_20Input) => +const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21Input) => raw["session.instructions.entry.put"]({ params: { sessionID: input["sessionID"], key: input["key"] }, payload: { value: input["value"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint4_21Request = Parameters[0] -type Endpoint4_21Input = { - readonly sessionID: Endpoint4_21Request["params"]["sessionID"] - readonly key: Endpoint4_21Request["params"]["key"] +type Endpoint4_22Request = Parameters[0] +type Endpoint4_22Input = { + readonly sessionID: Endpoint4_22Request["params"]["sessionID"] + readonly key: Endpoint4_22Request["params"]["key"] } -const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21Input) => +const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22Input) => raw["session.instructions.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint4_22Request = Parameters[0] -type Endpoint4_22Input = { - readonly sessionID: Endpoint4_22Request["params"]["sessionID"] - readonly after?: Endpoint4_22Request["query"]["after"] - readonly follow?: Endpoint4_22Request["query"]["follow"] +type Endpoint4_23Request = Parameters[0] +type Endpoint4_23Input = { + readonly sessionID: Endpoint4_23Request["params"]["sessionID"] + readonly after?: Endpoint4_23Request["query"]["after"] + readonly follow?: Endpoint4_23Request["query"]["follow"] } -const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22Input) => +const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23Input) => Stream.unwrap( raw["session.log"]({ params: { sessionID: input["sessionID"] }, @@ -313,22 +318,22 @@ const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22I ), ) -type Endpoint4_23Request = Parameters[0] -type Endpoint4_23Input = { readonly sessionID: Endpoint4_23Request["params"]["sessionID"] } -const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23Input) => - raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) - -type Endpoint4_24Request = Parameters[0] +type Endpoint4_24Request = Parameters[0] type Endpoint4_24Input = { readonly sessionID: Endpoint4_24Request["params"]["sessionID"] } const Endpoint4_24 = (raw: RawClient["server.session"]) => (input: Endpoint4_24Input) => + raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) + +type Endpoint4_25Request = Parameters[0] +type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] } +const Endpoint4_25 = (raw: RawClient["server.session"]) => (input: Endpoint4_25Input) => raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint4_25Request = Parameters[0] -type Endpoint4_25Input = { - readonly sessionID: Endpoint4_25Request["params"]["sessionID"] - readonly messageID: Endpoint4_25Request["params"]["messageID"] +type Endpoint4_26Request = Parameters[0] +type Endpoint4_26Input = { + readonly sessionID: Endpoint4_26Request["params"]["sessionID"] + readonly messageID: Endpoint4_26Request["params"]["messageID"] } -const Endpoint4_25 = (raw: RawClient["server.session"]) => (input: Endpoint4_25Input) => +const Endpoint4_26 = (raw: RawClient["server.session"]) => (input: Endpoint4_26Input) => raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), @@ -339,26 +344,27 @@ const adaptGroup4 = (raw: RawClient["server.session"]) => ({ create: Endpoint4_1(raw), active: Endpoint4_2(raw), get: Endpoint4_3(raw), - fork: Endpoint4_4(raw), - switchAgent: Endpoint4_5(raw), - switchModel: Endpoint4_6(raw), - rename: Endpoint4_7(raw), - prompt: Endpoint4_8(raw), - command: Endpoint4_9(raw), - skill: Endpoint4_10(raw), - synthetic: Endpoint4_11(raw), - shell: Endpoint4_12(raw), - compact: Endpoint4_13(raw), - wait: Endpoint4_14(raw), - revertStage: Endpoint4_15(raw), - revertClear: Endpoint4_16(raw), - revertCommit: Endpoint4_17(raw), - context: Endpoint4_18(raw), - instructions: { entry: { list: Endpoint4_19(raw), put: Endpoint4_20(raw), remove: Endpoint4_21(raw) } }, - log: Endpoint4_22(raw), - interrupt: Endpoint4_23(raw), - background: Endpoint4_24(raw), - message: Endpoint4_25(raw), + remove: Endpoint4_4(raw), + fork: Endpoint4_5(raw), + switchAgent: Endpoint4_6(raw), + switchModel: Endpoint4_7(raw), + rename: Endpoint4_8(raw), + prompt: Endpoint4_9(raw), + command: Endpoint4_10(raw), + skill: Endpoint4_11(raw), + synthetic: Endpoint4_12(raw), + shell: Endpoint4_13(raw), + compact: Endpoint4_14(raw), + wait: Endpoint4_15(raw), + revertStage: Endpoint4_16(raw), + revertClear: Endpoint4_17(raw), + revertCommit: Endpoint4_18(raw), + context: Endpoint4_19(raw), + instructions: { entry: { list: Endpoint4_20(raw), put: Endpoint4_21(raw), remove: Endpoint4_22(raw) } }, + log: Endpoint4_23(raw), + interrupt: Endpoint4_24(raw), + background: Endpoint4_25(raw), + message: Endpoint4_26(raw), }) type Endpoint5_0Request = Parameters[0] diff --git a/packages/client/src/promise/generated/client.ts b/packages/client/src/promise/generated/client.ts index e042859b91..82d5e4b048 100644 --- a/packages/client/src/promise/generated/client.ts +++ b/packages/client/src/promise/generated/client.ts @@ -13,6 +13,8 @@ import type { SessionActiveOutput, SessionGetInput, SessionGetOutput, + SessionRemoveInput, + SessionRemoveOutput, SessionForkInput, SessionForkOutput, SessionSwitchAgentInput, @@ -422,6 +424,17 @@ export function make(options: ClientOptions) { }, requestOptions, ).then((value) => value.data), + remove: (input: SessionRemoveInput, requestOptions?: RequestOptions) => + request( + { + method: "DELETE", + path: `/api/session/${encodeURIComponent(input.sessionID)}`, + successStatus: 204, + declaredStatuses: [404, 400, 401], + empty: true, + }, + requestOptions, + ), fork: (input: SessionForkInput, requestOptions?: RequestOptions) => request<{ readonly data: SessionForkOutput }>( { diff --git a/packages/client/src/promise/generated/types.ts b/packages/client/src/promise/generated/types.ts index f52039b6af..807c8db36a 100644 --- a/packages/client/src/promise/generated/types.ts +++ b/packages/client/src/promise/generated/types.ts @@ -459,6 +459,10 @@ export type SessionGetOutput = { } }["data"] +export type SessionRemoveInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] } + +export type SessionRemoveOutput = void + export type SessionForkInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] readonly messageID?: { readonly messageID?: string | undefined }["messageID"] @@ -1173,6 +1177,15 @@ export type SessionLogOutput = readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string; readonly title: string } } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.deleted" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { readonly sessionID: string } + } | { readonly id: string readonly created: number @@ -4443,6 +4456,15 @@ export type EventSubscribeOutput = readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string; readonly title: string } } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.deleted" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { readonly sessionID: string } + } | { readonly id: string readonly created: number diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 479ffa5cdc..96a0519865 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -153,6 +153,7 @@ export interface Interface { readonly create: (input: CreateInput) => Effect.Effect readonly fork: (input: ForkInput) => Effect.Effect readonly get: (sessionID: SessionSchema.ID) => Effect.Effect + readonly remove: (sessionID: SessionSchema.ID) => Effect.Effect readonly messages: (input: { sessionID: SessionSchema.ID limit?: number @@ -363,6 +364,15 @@ const layer = Layer.effect( if (!session) return yield* new NotFoundError({ sessionID }) return session }), + remove: Effect.fn("V2Session.remove")(function* (sessionID) { + yield* result.get(sessionID) + yield* execution.interrupt(sessionID) + yield* execution.awaitIdle(sessionID) + const children = yield* result.list({ parentID: sessionID }) + yield* Effect.forEach(children.data, (child) => result.remove(child.id), { concurrency: 1, discard: true }) + yield* events.publish(SessionEvent.Deleted, { sessionID }) + yield* events.remove(sessionID) + }), list: Effect.fn("V2Session.list")(function* (input = {}) { const direction = input.anchor?.direction ?? "next" const requestedOrder = input.order ?? "desc" diff --git a/packages/core/src/session/message-updater.ts b/packages/core/src/session/message-updater.ts index e6f455518c..5f2e69b9e7 100644 --- a/packages/core/src/session/message-updater.ts +++ b/packages/core/src/session/message-updater.ts @@ -152,6 +152,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) { }, "session.moved": () => Effect.void, "session.renamed": () => Effect.void, + "session.deleted": () => Effect.void, "session.forked": () => Effect.void, "session.prompt.promoted": () => Effect.void, "session.prompt.admitted": () => Effect.void, @@ -168,7 +169,6 @@ export function update(adapter: Adapter, event: SessionEvent.Event) { time: { created: event.created }, }), ), - "session.instructions.discovered": () => Effect.void, "session.synthetic": (event) => { return adapter.appendMessage( SessionMessage.Synthetic.make({ diff --git a/packages/core/src/session/projector.ts b/packages/core/src/session/projector.ts index e531ebcfdf..d813e13544 100644 --- a/packages/core/src/session/projector.ts +++ b/packages/core/src/session/projector.ts @@ -26,7 +26,10 @@ import type { DeepMutable } from "../schema" import { Slug } from "../util/slug" type DatabaseService = Database.Interface["db"] -type MessageEvent = Exclude +type MessageEvent = Exclude< + SessionEvent.DurableEvent, + typeof SessionEvent.Forked.Type | typeof SessionEvent.Deleted.Type +> const decodeMessage = Schema.decodeUnknownSync(SessionMessage.Message) const encodeMessage = Schema.encodeSync(SessionMessage.Message) @@ -505,6 +508,9 @@ const layer = Layer.effectDiscard( yield* events.project(SessionV1.Event.Deleted, (event) => db.delete(SessionTable).where(eq(SessionTable.id, event.data.sessionID)).run().pipe(Effect.orDie), ) + yield* events.project(SessionEvent.Deleted, (event) => + db.delete(SessionTable).where(eq(SessionTable.id, event.data.sessionID)).run().pipe(Effect.orDie), + ) yield* events.project(SessionV1.Event.MessageUpdated, (event) => Effect.gen(function* () { const time_created = event.data.info.time.created diff --git a/packages/core/test/session-remove.test.ts b/packages/core/test/session-remove.test.ts new file mode 100644 index 0000000000..b0fa62e5a8 --- /dev/null +++ b/packages/core/test/session-remove.test.ts @@ -0,0 +1,62 @@ +import { describe, expect } from "bun:test" +import { Effect, Layer } from "effect" +import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { Database } from "@opencode-ai/core/database/database" +import { EventV2 } from "@opencode-ai/core/event" +import { Location } from "@opencode-ai/core/location" +import { ProjectV2 } from "@opencode-ai/core/project" +import { AbsolutePath } from "@opencode-ai/core/schema" +import { SessionV2 } from "@opencode-ai/core/session" +import { SessionExecution } from "@opencode-ai/core/session/execution" +import { SessionProjector } from "@opencode-ai/core/session/projector" +import { SessionStore } from "@opencode-ai/core/session/store" +import { testEffect } from "./lib/effect" + +const projects = Layer.succeed( + ProjectV2.Service, + ProjectV2.Service.of({ + list: () => Effect.succeed([]), + resolve: (directory) => Effect.succeed({ id: ProjectV2.ID.global, directory }), + directories: () => Effect.succeed([]), + commit: () => Effect.void, + }), +) +const it = testEffect( + AppNodeBuilder.build( + LayerNode.group([Database.node, EventV2.node, SessionProjector.node, SessionStore.node, SessionV2.node]), + [ + [ProjectV2.node, projects], + [SessionExecution.node, SessionExecution.noopLayer], + ], + ), +) +const location = Location.Ref.make({ directory: AbsolutePath.make("/project") }) + +describe("SessionV2.remove", () => { + it.effect("removes a session and its children", () => + Effect.gen(function* () { + const session = yield* SessionV2.Service + const parent = yield* session.create({ location }) + const child = yield* session.create({ parentID: parent.id }) + + yield* session.remove(parent.id) + + expect((yield* session.list()).data).toEqual([]) + expect(yield* Effect.result(session.get(parent.id))).toMatchObject({ _tag: "Failure" }) + expect(yield* Effect.result(session.get(child.id))).toMatchObject({ _tag: "Failure" }) + }), + ) + + it.effect("fails when the session does not exist", () => + Effect.gen(function* () { + const session = yield* SessionV2.Service + const sessionID = SessionV2.ID.make("ses_missing") + + expect(yield* Effect.result(session.remove(sessionID))).toMatchObject({ + _tag: "Failure", + failure: { _tag: "Session.NotFoundError", sessionID }, + }) + }), + ) +}) diff --git a/packages/protocol/src/groups/session.ts b/packages/protocol/src/groups/session.ts index 8819d7d9e4..74e2c1c75c 100644 --- a/packages/protocol/src/groups/session.ts +++ b/packages/protocol/src/groups/session.ts @@ -189,6 +189,21 @@ export const makeSessionGroup = (sessionLo }), ), ) + .add( + HttpApiEndpoint.delete("session.remove", "/api/session/:sessionID", { + params: { sessionID: Session.ID }, + success: HttpApiSchema.NoContent, + error: SessionNotFoundError, + }) + .middleware(sessionLocationMiddleware) + .annotateMerge( + OpenApi.annotations({ + identifier: "v2.session.remove", + summary: "Delete session", + description: "Delete a session and its child sessions.", + }), + ), + ) .add( HttpApiEndpoint.post("session.fork", "/api/session/:sessionID/fork", { params: { sessionID: Session.ID }, diff --git a/packages/schema/src/session-event.ts b/packages/schema/src/session-event.ts index 09319c60a8..64f691e7ce 100644 --- a/packages/schema/src/session-event.ts +++ b/packages/schema/src/session-event.ts @@ -84,6 +84,16 @@ export const Renamed = Event.durable({ }) export type Renamed = typeof Renamed.Type +export const Deleted = Event.durable({ + type: "session.deleted", + durable: { + aggregate: "sessionID", + version: 2, + }, + schema: Base, +}) +export type Deleted = typeof Deleted.Type + export const Forked = Event.durable({ type: "session.forked", ...options, @@ -477,6 +487,7 @@ export const Definitions = Event.inventory( ModelSelected, Moved, Renamed, + Deleted, Forked, PromptPromoted, PromptAdmitted, diff --git a/packages/schema/test/event-manifest.test.ts b/packages/schema/test/event-manifest.test.ts index 0d07d2d00b..a62c0c7858 100644 --- a/packages/schema/test/event-manifest.test.ts +++ b/packages/schema/test/event-manifest.test.ts @@ -46,11 +46,12 @@ describe("public event manifest", () => { SessionV1.Event.Error, ]) expect(Array.from(EventManifest.Latest.keys())).toEqual( - EventManifest.Definitions.map((definition) => definition.type), + Array.from(new Set(EventManifest.Definitions.map((definition) => definition.type))), ) expect(EventManifest.Latest.get("agent.updated")).toBe(Agent.Event.Updated) expect(EventManifest.Latest.get("plugin.updated")).toBe(Plugin.Event.Updated) expect(EventManifest.Server.get("mcp.status.changed")).toBe(McpEvent.StatusChanged) + expect(EventManifest.Server.get("session.deleted")).toBe(SessionEvent.Deleted) expect(EventManifest.Server.has("mcp.tools.changed")).toBe(false) expect(Agent.Event.Updated.durable).toBeUndefined() expect(EventManifest.Durable.has("agent.updated")).toBe(false) @@ -95,6 +96,7 @@ describe("public event manifest", () => { "session.created.1", "session.updated.1", "session.deleted.1", + "session.deleted.2", "message.updated.1", "message.removed.1", "message.part.updated.1", @@ -170,4 +172,11 @@ describe("public event manifest", () => { expect(SessionEvent.Text.Started.durable?.version).toBe(1) expect(SessionEvent.Tool.Called.durable?.version).toBe(1) }) + + test("keeps current session deletion minimal", () => { + const sessionID = SessionID.make("ses_test") + + expect(SessionEvent.Deleted.data.make({ sessionID })).toEqual({ sessionID }) + expect(SessionEvent.Deleted.durable?.version).toBe(2) + }) }) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 5ff2606c86..d2e22a3fcb 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -416,6 +416,8 @@ import type { V2SessionQuestionRejectResponses, V2SessionQuestionReplyErrors, V2SessionQuestionReplyResponses, + V2SessionRemoveErrors, + V2SessionRemoveResponses, V2SessionRenameErrors, V2SessionRenameResponses, V2SessionRevertClearErrors, @@ -5905,6 +5907,25 @@ export class Session3 extends HeyApiClient { }) } + /** + * Delete session + * + * Delete a session and its child sessions. + */ + public remove( + parameters: { + sessionID: string + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).delete({ + url: "/api/session/{sessionID}", + ...options, + ...params, + }) + } + /** * Get session * diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 3b4854bcd0..bdc0f9dc95 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -824,7 +824,6 @@ export type GlobalEvent = { type: "session.deleted" properties: { sessionID: string - info: Session } } | { @@ -2914,6 +2913,7 @@ export type SessionDurableEvent = | SessionModelSelected | SessionMoved | SessionRenamed + | SessionDeleted | SessionForked | SessionPromptPromoted | SessionPromptAdmitted @@ -3576,13 +3576,12 @@ export type SyncEventSessionDeleted = { type: "sync" id: string syncEvent: { - type: "session.deleted.1" + type: "session.deleted.2" id: string seq: number aggregateID: string data: { sessionID: string - info: Session } } } @@ -4698,6 +4697,24 @@ export type SessionRenamed = { } } +export type SessionDeleted = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.deleted" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRef + data: { + sessionID: string + } +} + export type SessionForked = { id: string created: number @@ -5761,25 +5778,6 @@ export type SessionUpdated = { } } -export type SessionDeleted = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.deleted" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef - data: { - sessionID: string - info: Session - } -} - export type MessageUpdated = { id: string created: number @@ -6835,7 +6833,6 @@ export type EventSessionDeleted = { type: "session.deleted" properties: { sessionID: string - info: Session } } @@ -8403,7 +8400,7 @@ export type V2EventV2 = | AgentUpdatedV2 | SessionCreatedV2 | SessionUpdatedV2 - | SessionDeletedV2 + | SessionDeleted1 | MessageUpdatedV2 | MessageRemovedV2 | MessagePartUpdatedV2 @@ -8412,6 +8409,7 @@ export type V2EventV2 = | SessionModelSelectedV2 | SessionMovedV2 | SessionRenamedV2 + | SessionDeletedV2 | SessionForkedV2 | SessionPromptPromotedV2 | SessionPromptAdmittedV2 @@ -8817,6 +8815,24 @@ export type SessionRenamedV2 = { } } +export type SessionDeletedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.deleted" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + } +} + export type SessionForkedV2 = { id: string created: number @@ -9718,7 +9734,7 @@ export type SessionUpdatedV2 = { } } -export type SessionDeletedV2 = { +export type SessionDeleted1 = { id: string created: number metadata?: { @@ -14968,6 +14984,41 @@ export type V2SessionActiveResponses = { export type V2SessionActiveResponse = V2SessionActiveResponses[keyof V2SessionActiveResponses] +export type V2SessionRemoveData = { + body?: never + path: { + sessionID: string + } + query?: never + url: "/api/session/{sessionID}" +} + +export type V2SessionRemoveErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestErrorV2 + /** + * UnauthorizedError + */ + 401: UnauthorizedError + /** + * SessionNotFoundError + */ + 404: SessionNotFoundError +} + +export type V2SessionRemoveError = V2SessionRemoveErrors[keyof V2SessionRemoveErrors] + +export type V2SessionRemoveResponses = { + /** + * + */ + 204: void +} + +export type V2SessionRemoveResponse = V2SessionRemoveResponses[keyof V2SessionRemoveResponses] + export type V2SessionGetData = { body?: never path: { diff --git a/packages/server/src/handlers/session.ts b/packages/server/src/handlers/session.ts index aa79b8bd51..75227bb007 100644 --- a/packages/server/src/handlers/session.ts +++ b/packages/server/src/handlers/session.ts @@ -111,6 +111,22 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl } }), ) + .handle( + "session.remove", + Effect.fn(function* (ctx) { + yield* session.remove(ctx.params.sessionID).pipe( + Effect.catchTag( + "Session.NotFoundError", + (error) => + new SessionNotFoundError({ + sessionID: error.sessionID, + message: `Session not found: ${error.sessionID}`, + }), + ), + ) + return HttpApiSchema.NoContent.make() + }), + ) .handle( "session.fork", Effect.fn(function* (ctx) { diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index c2d02452d9..0d59eca423 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -1068,7 +1068,7 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi }) event.on("session.deleted", (evt) => { - if (route.data.type === "session" && route.data.sessionID === evt.data.info.id) { + if (route.data.type === "session" && route.data.sessionID === evt.data.sessionID) { route.navigate({ type: "home" }) toast.show({ variant: "info", diff --git a/packages/tui/src/component/dialog-session-list.tsx b/packages/tui/src/component/dialog-session-list.tsx index 9232acf6d9..72de26d278 100644 --- a/packages/tui/src/component/dialog-session-list.tsx +++ b/packages/tui/src/component/dialog-session-list.tsx @@ -1,4 +1,4 @@ -import { createMemo, createResource, onMount } from "solid-js" +import { createMemo, createResource, createSignal, onMount } from "solid-js" import path from "path" import type { SessionV2Info } from "@opencode-ai/sdk/v2" import { useDialog } from "../ui/dialog" @@ -15,6 +15,7 @@ import { useToast } from "../ui/toast" import { useCommandShortcut } from "../keymap" import { DialogSessionRename } from "./dialog-session-rename" import { Spinner } from "./spinner" +import { errorMessage } from "../util/error" export function DialogSessionList() { const dialog = useDialog() @@ -26,8 +27,10 @@ export function DialogSessionList() { const local = useLocal() const toast = useToast() const [search, setSearch] = createDebouncedSignal("", 150) + const [toDelete, setToDelete] = createSignal() const quickSwitch1 = useCommandShortcut("session.quick_switch.1") const quickSwitch9 = useCommandShortcut("session.quick_switch.9") + const deleteHint = useCommandShortcut("session.delete") const [searchResults] = createResource(search, async (query) => { if (!query) return @@ -78,11 +81,13 @@ export function DialogSessionList() { const directory = session.location.directory const footer = directory !== project.data.project.mainDir ? Locale.truncate(path.basename(directory), 20) : "" const slot = slotByID.get(session.id) + const deleting = toDelete() === session.id return { - title: session.title, + title: deleting ? `Press ${deleteHint()} again to confirm` : session.title, value: session.id, category, footer, + bg: deleting ? theme.error : undefined, gutter: data.session.family(session.id).some((id) => data.session.status(id) === "running") ? () => @@ -104,9 +109,6 @@ export function DialogSessionList() { onMount(() => dialog.setSize("large")) - const unavailable = (feature: string) => - toast.show({ message: `${feature} is not implemented for V2 sessions yet`, variant: "error", duration: 5000 }) - return ( setToDelete(undefined)} onSelect={(option) => { route.navigate({ type: "session", sessionID: option.value }) dialog.clear() @@ -127,7 +130,22 @@ export function DialogSessionList() { { command: "session.delete", title: "delete", - onTrigger: () => unavailable("Deleting"), + onTrigger: (option: { value: string }) => { + if (toDelete() !== option.value) { + setToDelete(option.value) + return + } + void sdk.client.v2.session + .remove({ sessionID: option.value }, { throwOnError: true }) + .catch((error) => { + setToDelete(undefined) + toast.show({ + message: `Failed to delete session: ${errorMessage(error)}`, + variant: "error", + duration: 5000, + }) + }) + }, }, { command: "session.rename", diff --git a/packages/tui/src/context/data.tsx b/packages/tui/src/context/data.tsx index 1ab7687e7e..87973249ee 100644 --- a/packages/tui/src/context/data.tsx +++ b/packages/tui/src/context/data.tsx @@ -213,11 +213,35 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ ) } + function removeSession(sessionID: string) { + messageIndex.delete(sessionID) + setStore( + "session", + produce((draft) => { + delete draft.info[sessionID] + delete draft.status[sessionID] + delete draft.compaction[sessionID] + delete draft.message[sessionID] + delete draft.input[sessionID] + delete draft.permission[sessionID] + delete draft.form[sessionID] + for (const [rootID, family] of Object.entries(draft.family)) { + const next = family.filter((id) => id !== sessionID) + if (next.length === 0) delete draft.family[rootID] + else draft.family[rootID] = next + } + }), + ) + } + function handleEvent(event: V2Event) { switch (event.type) { case "session.created": void result.session.refresh(event.data.sessionID) break + case "session.deleted": + removeSession(event.data.sessionID) + break case "catalog.updated": void Promise.all([ result.location.model.refresh(event.location), diff --git a/packages/tui/src/context/local.tsx b/packages/tui/src/context/local.tsx index efcaa8a01d..5c16acdaf8 100644 --- a/packages/tui/src/context/local.tsx +++ b/packages/tui/src/context/local.tsx @@ -470,7 +470,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ } event.on("session.deleted", (evt) => { - prune(evt.data.info.id) + prune(evt.data.sessionID) }) return {