feat(core): implement V2 session.shell (#35183)
This commit is contained in:
parent
5fc4e18b82
commit
bd8d858bf7
12 changed files with 412 additions and 150 deletions
|
|
@ -208,23 +208,35 @@ const Endpoint4_11 = (raw: RawClient["server.session"]) => (input: Endpoint4_11I
|
||||||
payload: { text: input["text"], description: input["description"], metadata: input["metadata"] },
|
payload: { text: input["text"], description: input["description"], metadata: input["metadata"] },
|
||||||
}).pipe(Effect.mapError(mapClientError))
|
}).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
|
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.shell"]>[0]
|
||||||
type Endpoint4_12Input = { readonly sessionID: Endpoint4_12Request["params"]["sessionID"] }
|
type Endpoint4_12Input = {
|
||||||
|
readonly sessionID: Endpoint4_12Request["params"]["sessionID"]
|
||||||
|
readonly id?: Endpoint4_12Request["payload"]["id"]
|
||||||
|
readonly command: Endpoint4_12Request["payload"]["command"]
|
||||||
|
}
|
||||||
const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12Input) =>
|
const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12Input) =>
|
||||||
raw["session.compact"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
raw["session.shell"]({
|
||||||
|
params: { sessionID: input["sessionID"] },
|
||||||
|
payload: { id: input["id"], command: input["command"] },
|
||||||
|
}).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
|
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
|
||||||
type Endpoint4_13Input = { readonly sessionID: Endpoint4_13Request["params"]["sessionID"] }
|
type Endpoint4_13Input = { readonly sessionID: Endpoint4_13Request["params"]["sessionID"] }
|
||||||
const Endpoint4_13 = (raw: RawClient["server.session"]) => (input: Endpoint4_13Input) =>
|
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<RawClient["server.session"]["session.wait"]>[0]
|
||||||
|
type Endpoint4_14Input = { readonly sessionID: Endpoint4_14Request["params"]["sessionID"] }
|
||||||
|
const Endpoint4_14 = (raw: RawClient["server.session"]) => (input: Endpoint4_14Input) =>
|
||||||
raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
|
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
|
||||||
type Endpoint4_14Input = {
|
type Endpoint4_15Input = {
|
||||||
readonly sessionID: Endpoint4_14Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_15Request["params"]["sessionID"]
|
||||||
readonly messageID: Endpoint4_14Request["payload"]["messageID"]
|
readonly messageID: Endpoint4_15Request["payload"]["messageID"]
|
||||||
readonly files?: Endpoint4_14Request["payload"]["files"]
|
readonly files?: Endpoint4_15Request["payload"]["files"]
|
||||||
}
|
}
|
||||||
const Endpoint4_14 = (raw: RawClient["server.session"]) => (input: Endpoint4_14Input) =>
|
const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15Input) =>
|
||||||
raw["session.revert.stage"]({
|
raw["session.revert.stage"]({
|
||||||
params: { sessionID: input["sessionID"] },
|
params: { sessionID: input["sessionID"] },
|
||||||
payload: { messageID: input["messageID"], files: input["files"] },
|
payload: { messageID: input["messageID"], files: input["files"] },
|
||||||
|
|
@ -233,61 +245,61 @@ const Endpoint4_14 = (raw: RawClient["server.session"]) => (input: Endpoint4_14I
|
||||||
Effect.map((value) => value.data),
|
Effect.map((value) => value.data),
|
||||||
)
|
)
|
||||||
|
|
||||||
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
|
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
|
||||||
type Endpoint4_15Input = { readonly sessionID: Endpoint4_15Request["params"]["sessionID"] }
|
|
||||||
const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15Input) =>
|
|
||||||
raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
|
||||||
|
|
||||||
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
|
|
||||||
type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] }
|
type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] }
|
||||||
const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16Input) =>
|
const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16Input) =>
|
||||||
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_17Request = Parameters<RawClient["server.session"]["session.context"]>[0]
|
type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
|
||||||
type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] }
|
type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] }
|
||||||
const Endpoint4_17 = (raw: RawClient["server.session"]) => (input: Endpoint4_17Input) =>
|
const Endpoint4_17 = (raw: RawClient["server.session"]) => (input: Endpoint4_17Input) =>
|
||||||
|
raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
|
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.context"]>[0]
|
||||||
|
type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
|
||||||
|
const Endpoint4_18 = (raw: RawClient["server.session"]) => (input: Endpoint4_18Input) =>
|
||||||
raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||||
Effect.mapError(mapClientError),
|
Effect.mapError(mapClientError),
|
||||||
Effect.map((value) => value.data),
|
Effect.map((value) => value.data),
|
||||||
)
|
)
|
||||||
|
|
||||||
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.context.entry.list"]>[0]
|
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.context.entry.list"]>[0]
|
||||||
type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
|
type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] }
|
||||||
const Endpoint4_18 = (raw: RawClient["server.session"]) => (input: Endpoint4_18Input) =>
|
const Endpoint4_19 = (raw: RawClient["server.session"]) => (input: Endpoint4_19Input) =>
|
||||||
raw["session.context.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
raw["session.context.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||||
Effect.mapError(mapClientError),
|
Effect.mapError(mapClientError),
|
||||||
Effect.map((value) => value.data),
|
Effect.map((value) => value.data),
|
||||||
)
|
)
|
||||||
|
|
||||||
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.context.entry.put"]>[0]
|
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.context.entry.put"]>[0]
|
||||||
type Endpoint4_19Input = {
|
type Endpoint4_20Input = {
|
||||||
readonly sessionID: Endpoint4_19Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_20Request["params"]["sessionID"]
|
||||||
readonly key: Endpoint4_19Request["params"]["key"]
|
readonly key: Endpoint4_20Request["params"]["key"]
|
||||||
readonly value: Endpoint4_19Request["payload"]["value"]
|
readonly value: Endpoint4_20Request["payload"]["value"]
|
||||||
}
|
}
|
||||||
const Endpoint4_19 = (raw: RawClient["server.session"]) => (input: Endpoint4_19Input) =>
|
const Endpoint4_20 = (raw: RawClient["server.session"]) => (input: Endpoint4_20Input) =>
|
||||||
raw["session.context.entry.put"]({
|
raw["session.context.entry.put"]({
|
||||||
params: { sessionID: input["sessionID"], key: input["key"] },
|
params: { sessionID: input["sessionID"], key: input["key"] },
|
||||||
payload: { value: input["value"] },
|
payload: { value: input["value"] },
|
||||||
}).pipe(Effect.mapError(mapClientError))
|
}).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.context.entry.remove"]>[0]
|
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.context.entry.remove"]>[0]
|
||||||
type Endpoint4_20Input = {
|
type Endpoint4_21Input = {
|
||||||
readonly sessionID: Endpoint4_20Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_21Request["params"]["sessionID"]
|
||||||
readonly key: Endpoint4_20Request["params"]["key"]
|
readonly key: Endpoint4_21Request["params"]["key"]
|
||||||
}
|
}
|
||||||
const Endpoint4_20 = (raw: RawClient["server.session"]) => (input: Endpoint4_20Input) =>
|
const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21Input) =>
|
||||||
raw["session.context.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe(
|
raw["session.context.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe(
|
||||||
Effect.mapError(mapClientError),
|
Effect.mapError(mapClientError),
|
||||||
)
|
)
|
||||||
|
|
||||||
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.log"]>[0]
|
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.log"]>[0]
|
||||||
type Endpoint4_21Input = {
|
type Endpoint4_22Input = {
|
||||||
readonly sessionID: Endpoint4_21Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_22Request["params"]["sessionID"]
|
||||||
readonly after?: Endpoint4_21Request["query"]["after"]
|
readonly after?: Endpoint4_22Request["query"]["after"]
|
||||||
readonly follow?: Endpoint4_21Request["query"]["follow"]
|
readonly follow?: Endpoint4_22Request["query"]["follow"]
|
||||||
}
|
}
|
||||||
const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21Input) =>
|
const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22Input) =>
|
||||||
Stream.unwrap(
|
Stream.unwrap(
|
||||||
raw["session.log"]({
|
raw["session.log"]({
|
||||||
params: { sessionID: input["sessionID"] },
|
params: { sessionID: input["sessionID"] },
|
||||||
|
|
@ -298,22 +310,22 @@ const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21I
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
|
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
|
||||||
type Endpoint4_22Input = { readonly sessionID: Endpoint4_22Request["params"]["sessionID"] }
|
|
||||||
const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22Input) =>
|
|
||||||
raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
|
||||||
|
|
||||||
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.background"]>[0]
|
|
||||||
type Endpoint4_23Input = { readonly sessionID: Endpoint4_23Request["params"]["sessionID"] }
|
type Endpoint4_23Input = { readonly sessionID: Endpoint4_23Request["params"]["sessionID"] }
|
||||||
const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23Input) =>
|
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<RawClient["server.session"]["session.background"]>[0]
|
||||||
|
type Endpoint4_24Input = { readonly sessionID: Endpoint4_24Request["params"]["sessionID"] }
|
||||||
|
const Endpoint4_24 = (raw: RawClient["server.session"]) => (input: Endpoint4_24Input) =>
|
||||||
raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.message"]>[0]
|
type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.message"]>[0]
|
||||||
type Endpoint4_24Input = {
|
type Endpoint4_25Input = {
|
||||||
readonly sessionID: Endpoint4_24Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_25Request["params"]["sessionID"]
|
||||||
readonly messageID: Endpoint4_24Request["params"]["messageID"]
|
readonly messageID: Endpoint4_25Request["params"]["messageID"]
|
||||||
}
|
}
|
||||||
const Endpoint4_24 = (raw: RawClient["server.session"]) => (input: Endpoint4_24Input) =>
|
const Endpoint4_25 = (raw: RawClient["server.session"]) => (input: Endpoint4_25Input) =>
|
||||||
raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe(
|
raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe(
|
||||||
Effect.mapError(mapClientError),
|
Effect.mapError(mapClientError),
|
||||||
Effect.map((value) => value.data),
|
Effect.map((value) => value.data),
|
||||||
|
|
@ -332,19 +344,20 @@ const adaptGroup4 = (raw: RawClient["server.session"]) => ({
|
||||||
command: Endpoint4_9(raw),
|
command: Endpoint4_9(raw),
|
||||||
skill: Endpoint4_10(raw),
|
skill: Endpoint4_10(raw),
|
||||||
synthetic: Endpoint4_11(raw),
|
synthetic: Endpoint4_11(raw),
|
||||||
compact: Endpoint4_12(raw),
|
shell: Endpoint4_12(raw),
|
||||||
wait: Endpoint4_13(raw),
|
compact: Endpoint4_13(raw),
|
||||||
revertStage: Endpoint4_14(raw),
|
wait: Endpoint4_14(raw),
|
||||||
revertClear: Endpoint4_15(raw),
|
revertStage: Endpoint4_15(raw),
|
||||||
revertCommit: Endpoint4_16(raw),
|
revertClear: Endpoint4_16(raw),
|
||||||
context: Endpoint4_17(raw),
|
revertCommit: Endpoint4_17(raw),
|
||||||
listContextEntries: Endpoint4_18(raw),
|
context: Endpoint4_18(raw),
|
||||||
putContextEntry: Endpoint4_19(raw),
|
listContextEntries: Endpoint4_19(raw),
|
||||||
removeContextEntry: Endpoint4_20(raw),
|
putContextEntry: Endpoint4_20(raw),
|
||||||
log: Endpoint4_21(raw),
|
removeContextEntry: Endpoint4_21(raw),
|
||||||
interrupt: Endpoint4_22(raw),
|
log: Endpoint4_22(raw),
|
||||||
background: Endpoint4_23(raw),
|
interrupt: Endpoint4_23(raw),
|
||||||
message: Endpoint4_24(raw),
|
background: Endpoint4_24(raw),
|
||||||
|
message: Endpoint4_25(raw),
|
||||||
})
|
})
|
||||||
|
|
||||||
type Endpoint5_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0]
|
type Endpoint5_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0]
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ import type {
|
||||||
SessionSkillOutput,
|
SessionSkillOutput,
|
||||||
SessionSyntheticInput,
|
SessionSyntheticInput,
|
||||||
SessionSyntheticOutput,
|
SessionSyntheticOutput,
|
||||||
|
SessionShellInput,
|
||||||
|
SessionShellOutput,
|
||||||
SessionCompactInput,
|
SessionCompactInput,
|
||||||
SessionCompactOutput,
|
SessionCompactOutput,
|
||||||
SessionWaitInput,
|
SessionWaitInput,
|
||||||
|
|
@ -525,6 +527,18 @@ export function make(options: ClientOptions) {
|
||||||
},
|
},
|
||||||
requestOptions,
|
requestOptions,
|
||||||
),
|
),
|
||||||
|
shell: (input: SessionShellInput, requestOptions?: RequestOptions) =>
|
||||||
|
request<SessionShellOutput>(
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
path: `/api/session/${encodeURIComponent(input.sessionID)}/shell`,
|
||||||
|
body: { id: input["id"], command: input["command"] },
|
||||||
|
successStatus: 204,
|
||||||
|
declaredStatuses: [404, 400, 401],
|
||||||
|
empty: true,
|
||||||
|
},
|
||||||
|
requestOptions,
|
||||||
|
),
|
||||||
compact: (input: SessionCompactInput, requestOptions?: RequestOptions) =>
|
compact: (input: SessionCompactInput, requestOptions?: RequestOptions) =>
|
||||||
request<SessionCompactOutput>(
|
request<SessionCompactOutput>(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -865,6 +865,14 @@ export type SessionSyntheticInput = {
|
||||||
|
|
||||||
export type SessionSyntheticOutput = void
|
export type SessionSyntheticOutput = void
|
||||||
|
|
||||||
|
export type SessionShellInput = {
|
||||||
|
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||||
|
readonly id?: { readonly id?: string | undefined; readonly command: string }["id"]
|
||||||
|
readonly command: { readonly id?: string | undefined; readonly command: string }["command"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SessionShellOutput = void
|
||||||
|
|
||||||
export type SessionCompactInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
export type SessionCompactInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||||
|
|
||||||
export type SessionCompactOutput = void
|
export type SessionCompactOutput = void
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,9 @@ import type { EventLog } from "@opencode-ai/schema/event-log"
|
||||||
import { SkillV2 } from "./skill"
|
import { SkillV2 } from "./skill"
|
||||||
import { Job } from "./job"
|
import { Job } from "./job"
|
||||||
import { CommandV2 } from "./command"
|
import { CommandV2 } from "./command"
|
||||||
|
import { Identifier } from "./util/identifier"
|
||||||
|
import { Shell } from "./shell"
|
||||||
|
import { KeyedMutex } from "./effect/keyed-mutex"
|
||||||
|
|
||||||
export const RevertState = Revert.State
|
export const RevertState = Revert.State
|
||||||
export type RevertState = Revert.State
|
export type RevertState = Revert.State
|
||||||
|
|
@ -106,7 +109,7 @@ export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("Ses
|
||||||
export class OperationUnavailableError extends Schema.TaggedErrorClass<OperationUnavailableError>()(
|
export class OperationUnavailableError extends Schema.TaggedErrorClass<OperationUnavailableError>()(
|
||||||
"Session.OperationUnavailableError",
|
"Session.OperationUnavailableError",
|
||||||
{
|
{
|
||||||
operation: Schema.Literals(["move", "shell", "skill", "switchAgent", "compact"]),
|
operation: Schema.Literals(["move", "skill", "switchAgent", "compact"]),
|
||||||
},
|
},
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|
@ -208,8 +211,7 @@ export interface Interface {
|
||||||
id?: EventV2.ID
|
id?: EventV2.ID
|
||||||
sessionID: SessionSchema.ID
|
sessionID: SessionSchema.ID
|
||||||
command: string
|
command: string
|
||||||
resume?: boolean
|
}) => Effect.Effect<void, NotFoundError>
|
||||||
}) => Effect.Effect<void, OperationUnavailableError>
|
|
||||||
readonly skill: (input: {
|
readonly skill: (input: {
|
||||||
id?: SessionMessage.ID
|
id?: SessionMessage.ID
|
||||||
sessionID: SessionSchema.ID
|
sessionID: SessionSchema.ID
|
||||||
|
|
@ -255,6 +257,8 @@ const layer = Layer.effect(
|
||||||
const locations = yield* LocationServiceMap.Service
|
const locations = yield* LocationServiceMap.Service
|
||||||
const jobs = yield* Job.Service
|
const jobs = yield* Job.Service
|
||||||
const scope = yield* Scope.Scope
|
const scope = yield* Scope.Scope
|
||||||
|
const activeShells = new Set<SessionSchema.ID>()
|
||||||
|
const shellLocks = KeyedMutex.makeUnsafe<SessionSchema.ID>()
|
||||||
const decodeMessage = Schema.decodeUnknownEffect(SessionMessage.Message)
|
const decodeMessage = Schema.decodeUnknownEffect(SessionMessage.Message)
|
||||||
const isDurableSessionEvent = Schema.is(SessionEvent.Durable)
|
const isDurableSessionEvent = Schema.is(SessionEvent.Durable)
|
||||||
const decode = (row: typeof SessionMessageTable.$inferSelect) =>
|
const decode = (row: typeof SessionMessageTable.$inferSelect) =>
|
||||||
|
|
@ -268,6 +272,19 @@ const layer = Layer.effect(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Session shell is user-initiated and synchronous at the API boundary, while
|
||||||
|
// the Location shell service owns process lifecycle and file-backed output.
|
||||||
|
const runShellCommand = (command: string, cwd: string) =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const shell = yield* Shell.Service
|
||||||
|
const info = yield* shell.create({ command, cwd })
|
||||||
|
yield* shell.wait(info.id)
|
||||||
|
const output = yield* shell.output(info.id, { limit: SHELL_MAX_CAPTURE_BYTES })
|
||||||
|
return output.output || "(no output)"
|
||||||
|
}).pipe(
|
||||||
|
Effect.catchTag("Shell.NotFoundError", () => Effect.succeed("Shell command output is no longer available.")),
|
||||||
|
)
|
||||||
|
|
||||||
const result = Service.of({
|
const result = Service.of({
|
||||||
create: Effect.fn("V2Session.create")(function* (input) {
|
create: Effect.fn("V2Session.create")(function* (input) {
|
||||||
const sessionID = input.id ?? SessionSchema.ID.create()
|
const sessionID = input.id ?? SessionSchema.ID.create()
|
||||||
|
|
@ -489,7 +506,10 @@ const layer = Layer.effect(
|
||||||
)
|
)
|
||||||
if (!SessionInput.equivalent(admitted, expected))
|
if (!SessionInput.equivalent(admitted, expected))
|
||||||
return yield* new PromptConflictError({ sessionID: input.sessionID, messageID })
|
return yield* new PromptConflictError({ sessionID: input.sessionID, messageID })
|
||||||
if (input.resume !== false) yield* execution.wake(admitted.sessionID)
|
if (input.resume !== false) {
|
||||||
|
if (activeShells.has(admitted.sessionID)) return admitted
|
||||||
|
yield* execution.wake(admitted.sessionID)
|
||||||
|
}
|
||||||
return admitted
|
return admitted
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
@ -525,8 +545,43 @@ const layer = Layer.effect(
|
||||||
resume: input.resume,
|
resume: input.resume,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
shell: Effect.fn("V2Session.shell")(function* () {
|
shell: Effect.fn("V2Session.shell")(function* (input) {
|
||||||
return yield* new OperationUnavailableError({ operation: "shell" })
|
const session = yield* result.get(input.sessionID)
|
||||||
|
yield* shellLocks.withLock(input.sessionID)(
|
||||||
|
Effect.gen(function* () {
|
||||||
|
activeShells.add(input.sessionID)
|
||||||
|
if ((yield* execution.active).has(input.sessionID)) yield* execution.awaitIdle(input.sessionID)
|
||||||
|
const messageID = SessionMessage.ID.create()
|
||||||
|
const callID = Identifier.ascending()
|
||||||
|
yield* events.publish(
|
||||||
|
SessionEvent.Shell.Started,
|
||||||
|
{
|
||||||
|
sessionID: input.sessionID,
|
||||||
|
messageID,
|
||||||
|
callID,
|
||||||
|
command: input.command,
|
||||||
|
timestamp: yield* DateTime.now,
|
||||||
|
},
|
||||||
|
{ id: input.id },
|
||||||
|
)
|
||||||
|
const output = yield* runShellCommand(input.command, session.location.directory).pipe(
|
||||||
|
Effect.provide(locations.get(session.location)),
|
||||||
|
)
|
||||||
|
yield* events.publish(SessionEvent.Shell.Ended, {
|
||||||
|
sessionID: input.sessionID,
|
||||||
|
callID,
|
||||||
|
output,
|
||||||
|
timestamp: yield* DateTime.now,
|
||||||
|
})
|
||||||
|
}).pipe(
|
||||||
|
Effect.ensuring(
|
||||||
|
Effect.gen(function* () {
|
||||||
|
activeShells.delete(input.sessionID)
|
||||||
|
yield* execution.wake(input.sessionID)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
skill: Effect.fn("V2Session.skill")(function* (input) {
|
skill: Effect.fn("V2Session.skill")(function* (input) {
|
||||||
const session = yield* result.get(input.sessionID)
|
const session = yield* result.get(input.sessionID)
|
||||||
|
|
@ -679,6 +734,9 @@ const resolvePrompt = (input: PromptInput.Prompt) =>
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Mirrors the shell tool's in-memory preview safety limit.
|
||||||
|
const SHELL_MAX_CAPTURE_BYTES = 1024 * 1024
|
||||||
|
|
||||||
export const node = makeGlobalNode({
|
export const node = makeGlobalNode({
|
||||||
service: Service,
|
service: Service,
|
||||||
layer: layer.pipe(Layer.orDie),
|
layer: layer.pipe(Layer.orDie),
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@ import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||||
import { SessionV2 } from "@opencode-ai/core/session"
|
import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||||
import { Prompt } from "@opencode-ai/core/session/prompt"
|
import { Prompt } from "@opencode-ai/core/session/prompt"
|
||||||
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||||
import { SessionInput } from "@opencode-ai/core/session/input"
|
import { SessionInput } from "@opencode-ai/core/session/input"
|
||||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
|
||||||
import { SessionTable } from "@opencode-ai/core/session/sql"
|
import { SessionTable } from "@opencode-ai/core/session/sql"
|
||||||
import { SessionStore } from "@opencode-ai/core/session/store"
|
import { SessionStore } from "@opencode-ai/core/session/store"
|
||||||
import { WorkspaceV2 } from "@opencode-ai/core/workspace"
|
import { WorkspaceV2 } from "@opencode-ai/core/workspace"
|
||||||
|
|
@ -62,6 +62,13 @@ const assertCreateInputTypes = (session: SessionV2.Interface) => {
|
||||||
}
|
}
|
||||||
void assertCreateInputTypes
|
void assertCreateInputTypes
|
||||||
|
|
||||||
|
function withTmp<A, E, R>(f: (directory: string) => Effect.Effect<A, E, R>) {
|
||||||
|
return Effect.acquireRelease(
|
||||||
|
Effect.promise(() => tmpdir()),
|
||||||
|
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||||
|
).pipe(Effect.flatMap((tmp) => f(tmp.path)))
|
||||||
|
}
|
||||||
|
|
||||||
describe("SessionV2.create", () => {
|
describe("SessionV2.create", () => {
|
||||||
it.effect("creates a fresh projected session when the ID is omitted", () =>
|
it.effect("creates a fresh projected session when the ID is omitted", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
|
|
@ -476,20 +483,41 @@ describe("SessionV2.create", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("reports unfinished Session operations as unavailable", () =>
|
it.live("runs a shell command and projects the started/ended shell message", () =>
|
||||||
Effect.gen(function* () {
|
withTmp((directory) =>
|
||||||
const session = yield* SessionV2.Service
|
Effect.gen(function* () {
|
||||||
const created = yield* session.create({ location })
|
const session = yield* SessionV2.Service
|
||||||
const unavailable = (
|
const created = yield* session.create({
|
||||||
effect: Effect.Effect<void, SessionV2.NotFoundError | SessionV2.OperationUnavailableError>,
|
location: Location.Ref.make({ directory: AbsolutePath.make(directory) }),
|
||||||
) =>
|
})
|
||||||
effect.pipe(
|
|
||||||
Effect.flip,
|
|
||||||
Effect.map((error) => (error instanceof SessionV2.OperationUnavailableError ? error.operation : "not-found")),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(yield* unavailable(session.shell({ sessionID: created.id, command: "pwd" }))).toBe("shell")
|
yield* session.shell({ sessionID: created.id, command: "echo hello" })
|
||||||
}),
|
|
||||||
|
const messages = yield* session.messages({ sessionID: created.id, order: "asc" })
|
||||||
|
const shell = messages.find((message): message is SessionMessage.Shell => message.type === "shell")
|
||||||
|
expect(shell).toMatchObject({ type: "shell", command: "echo hello" })
|
||||||
|
expect(shell?.output).toContain("hello")
|
||||||
|
expect(shell?.time.completed).toBeDefined()
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.live("still emits shell ended for a failing command", () =>
|
||||||
|
withTmp((directory) =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const session = yield* SessionV2.Service
|
||||||
|
const created = yield* session.create({
|
||||||
|
location: Location.Ref.make({ directory: AbsolutePath.make(directory) }),
|
||||||
|
})
|
||||||
|
|
||||||
|
yield* session.shell({ sessionID: created.id, command: "false" })
|
||||||
|
|
||||||
|
const messages = yield* session.messages({ sessionID: created.id, order: "asc" })
|
||||||
|
const shell = messages.find((message): message is SessionMessage.Shell => message.type === "shell")
|
||||||
|
expect(shell).toMatchObject({ type: "shell", command: "false" })
|
||||||
|
expect(shell?.time.completed).toBeDefined()
|
||||||
|
}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("switches the selected agent through the durable Session event", () =>
|
it.effect("switches the selected agent through the durable Session event", () =>
|
||||||
|
|
|
||||||
|
|
@ -153,96 +153,105 @@ export type Endpoint4_11Input = {
|
||||||
export type Endpoint4_11Output = EffectValue<ReturnType<RawClient["server.session"]["session.synthetic"]>>
|
export type Endpoint4_11Output = EffectValue<ReturnType<RawClient["server.session"]["session.synthetic"]>>
|
||||||
export type SessionSyntheticOperation<E = never> = (input: Endpoint4_11Input) => Effect.Effect<Endpoint4_11Output, E>
|
export type SessionSyntheticOperation<E = never> = (input: Endpoint4_11Input) => Effect.Effect<Endpoint4_11Output, E>
|
||||||
|
|
||||||
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
|
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.shell"]>[0]
|
||||||
export type Endpoint4_12Input = { readonly sessionID: Endpoint4_12Request["params"]["sessionID"] }
|
export type Endpoint4_12Input = {
|
||||||
export type Endpoint4_12Output = EffectValue<ReturnType<RawClient["server.session"]["session.compact"]>>
|
readonly sessionID: Endpoint4_12Request["params"]["sessionID"]
|
||||||
export type SessionCompactOperation<E = never> = (input: Endpoint4_12Input) => Effect.Effect<Endpoint4_12Output, E>
|
readonly id?: Endpoint4_12Request["payload"]["id"]
|
||||||
|
readonly command: Endpoint4_12Request["payload"]["command"]
|
||||||
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
|
|
||||||
export type Endpoint4_13Input = { readonly sessionID: Endpoint4_13Request["params"]["sessionID"] }
|
|
||||||
export type Endpoint4_13Output = EffectValue<ReturnType<RawClient["server.session"]["session.wait"]>>
|
|
||||||
export type SessionWaitOperation<E = never> = (input: Endpoint4_13Input) => Effect.Effect<Endpoint4_13Output, E>
|
|
||||||
|
|
||||||
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
|
|
||||||
export type Endpoint4_14Input = {
|
|
||||||
readonly sessionID: Endpoint4_14Request["params"]["sessionID"]
|
|
||||||
readonly messageID: Endpoint4_14Request["payload"]["messageID"]
|
|
||||||
readonly files?: Endpoint4_14Request["payload"]["files"]
|
|
||||||
}
|
}
|
||||||
export type Endpoint4_14Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.stage"]>>["data"]
|
export type Endpoint4_12Output = EffectValue<ReturnType<RawClient["server.session"]["session.shell"]>>
|
||||||
export type SessionRevertStageOperation<E = never> = (input: Endpoint4_14Input) => Effect.Effect<Endpoint4_14Output, E>
|
export type SessionShellOperation<E = never> = (input: Endpoint4_12Input) => Effect.Effect<Endpoint4_12Output, E>
|
||||||
|
|
||||||
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
|
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
|
||||||
export type Endpoint4_15Input = { readonly sessionID: Endpoint4_15Request["params"]["sessionID"] }
|
export type Endpoint4_13Input = { readonly sessionID: Endpoint4_13Request["params"]["sessionID"] }
|
||||||
export type Endpoint4_15Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.clear"]>>
|
export type Endpoint4_13Output = EffectValue<ReturnType<RawClient["server.session"]["session.compact"]>>
|
||||||
export type SessionRevertClearOperation<E = never> = (input: Endpoint4_15Input) => Effect.Effect<Endpoint4_15Output, E>
|
export type SessionCompactOperation<E = never> = (input: Endpoint4_13Input) => Effect.Effect<Endpoint4_13Output, E>
|
||||||
|
|
||||||
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
|
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
|
||||||
|
export type Endpoint4_14Input = { readonly sessionID: Endpoint4_14Request["params"]["sessionID"] }
|
||||||
|
export type Endpoint4_14Output = EffectValue<ReturnType<RawClient["server.session"]["session.wait"]>>
|
||||||
|
export type SessionWaitOperation<E = never> = (input: Endpoint4_14Input) => Effect.Effect<Endpoint4_14Output, E>
|
||||||
|
|
||||||
|
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
|
||||||
|
export type Endpoint4_15Input = {
|
||||||
|
readonly sessionID: Endpoint4_15Request["params"]["sessionID"]
|
||||||
|
readonly messageID: Endpoint4_15Request["payload"]["messageID"]
|
||||||
|
readonly files?: Endpoint4_15Request["payload"]["files"]
|
||||||
|
}
|
||||||
|
export type Endpoint4_15Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.stage"]>>["data"]
|
||||||
|
export type SessionRevertStageOperation<E = never> = (input: Endpoint4_15Input) => Effect.Effect<Endpoint4_15Output, E>
|
||||||
|
|
||||||
|
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
|
||||||
export type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] }
|
export type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] }
|
||||||
export type Endpoint4_16Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.commit"]>>
|
export type Endpoint4_16Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.clear"]>>
|
||||||
export type SessionRevertCommitOperation<E = never> = (input: Endpoint4_16Input) => Effect.Effect<Endpoint4_16Output, E>
|
export type SessionRevertClearOperation<E = never> = (input: Endpoint4_16Input) => Effect.Effect<Endpoint4_16Output, E>
|
||||||
|
|
||||||
type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.context"]>[0]
|
type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
|
||||||
export type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] }
|
export type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] }
|
||||||
export type Endpoint4_17Output = EffectValue<ReturnType<RawClient["server.session"]["session.context"]>>["data"]
|
export type Endpoint4_17Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.commit"]>>
|
||||||
export type SessionContextOperation<E = never> = (input: Endpoint4_17Input) => Effect.Effect<Endpoint4_17Output, E>
|
export type SessionRevertCommitOperation<E = never> = (input: Endpoint4_17Input) => Effect.Effect<Endpoint4_17Output, E>
|
||||||
|
|
||||||
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.context.entry.list"]>[0]
|
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.context"]>[0]
|
||||||
export type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
|
export type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
|
||||||
export type Endpoint4_18Output = EffectValue<
|
export type Endpoint4_18Output = EffectValue<ReturnType<RawClient["server.session"]["session.context"]>>["data"]
|
||||||
|
export type SessionContextOperation<E = never> = (input: Endpoint4_18Input) => Effect.Effect<Endpoint4_18Output, E>
|
||||||
|
|
||||||
|
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.context.entry.list"]>[0]
|
||||||
|
export type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] }
|
||||||
|
export type Endpoint4_19Output = EffectValue<
|
||||||
ReturnType<RawClient["server.session"]["session.context.entry.list"]>
|
ReturnType<RawClient["server.session"]["session.context.entry.list"]>
|
||||||
>["data"]
|
>["data"]
|
||||||
export type SessionListContextEntriesOperation<E = never> = (
|
export type SessionListContextEntriesOperation<E = never> = (
|
||||||
input: Endpoint4_18Input,
|
|
||||||
) => Effect.Effect<Endpoint4_18Output, E>
|
|
||||||
|
|
||||||
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.context.entry.put"]>[0]
|
|
||||||
export type Endpoint4_19Input = {
|
|
||||||
readonly sessionID: Endpoint4_19Request["params"]["sessionID"]
|
|
||||||
readonly key: Endpoint4_19Request["params"]["key"]
|
|
||||||
readonly value: Endpoint4_19Request["payload"]["value"]
|
|
||||||
}
|
|
||||||
export type Endpoint4_19Output = EffectValue<ReturnType<RawClient["server.session"]["session.context.entry.put"]>>
|
|
||||||
export type SessionPutContextEntryOperation<E = never> = (
|
|
||||||
input: Endpoint4_19Input,
|
input: Endpoint4_19Input,
|
||||||
) => Effect.Effect<Endpoint4_19Output, E>
|
) => Effect.Effect<Endpoint4_19Output, E>
|
||||||
|
|
||||||
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.context.entry.remove"]>[0]
|
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.context.entry.put"]>[0]
|
||||||
export type Endpoint4_20Input = {
|
export type Endpoint4_20Input = {
|
||||||
readonly sessionID: Endpoint4_20Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_20Request["params"]["sessionID"]
|
||||||
readonly key: Endpoint4_20Request["params"]["key"]
|
readonly key: Endpoint4_20Request["params"]["key"]
|
||||||
|
readonly value: Endpoint4_20Request["payload"]["value"]
|
||||||
}
|
}
|
||||||
export type Endpoint4_20Output = EffectValue<ReturnType<RawClient["server.session"]["session.context.entry.remove"]>>
|
export type Endpoint4_20Output = EffectValue<ReturnType<RawClient["server.session"]["session.context.entry.put"]>>
|
||||||
export type SessionRemoveContextEntryOperation<E = never> = (
|
export type SessionPutContextEntryOperation<E = never> = (
|
||||||
input: Endpoint4_20Input,
|
input: Endpoint4_20Input,
|
||||||
) => Effect.Effect<Endpoint4_20Output, E>
|
) => Effect.Effect<Endpoint4_20Output, E>
|
||||||
|
|
||||||
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.log"]>[0]
|
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.context.entry.remove"]>[0]
|
||||||
export type Endpoint4_21Input = {
|
export type Endpoint4_21Input = {
|
||||||
readonly sessionID: Endpoint4_21Request["params"]["sessionID"]
|
readonly sessionID: Endpoint4_21Request["params"]["sessionID"]
|
||||||
readonly after?: Endpoint4_21Request["query"]["after"]
|
readonly key: Endpoint4_21Request["params"]["key"]
|
||||||
readonly follow?: Endpoint4_21Request["query"]["follow"]
|
|
||||||
}
|
}
|
||||||
export type Endpoint4_21Output = StreamValue<EffectValue<ReturnType<RawClient["server.session"]["session.log"]>>>
|
export type Endpoint4_21Output = EffectValue<ReturnType<RawClient["server.session"]["session.context.entry.remove"]>>
|
||||||
export type SessionLogOperation<E = never> = (input: Endpoint4_21Input) => Stream.Stream<Endpoint4_21Output, E>
|
export type SessionRemoveContextEntryOperation<E = never> = (
|
||||||
|
input: Endpoint4_21Input,
|
||||||
|
) => Effect.Effect<Endpoint4_21Output, E>
|
||||||
|
|
||||||
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
|
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.log"]>[0]
|
||||||
export type Endpoint4_22Input = { readonly sessionID: Endpoint4_22Request["params"]["sessionID"] }
|
export type Endpoint4_22Input = {
|
||||||
export type Endpoint4_22Output = EffectValue<ReturnType<RawClient["server.session"]["session.interrupt"]>>
|
readonly sessionID: Endpoint4_22Request["params"]["sessionID"]
|
||||||
export type SessionInterruptOperation<E = never> = (input: Endpoint4_22Input) => Effect.Effect<Endpoint4_22Output, E>
|
readonly after?: Endpoint4_22Request["query"]["after"]
|
||||||
|
readonly follow?: Endpoint4_22Request["query"]["follow"]
|
||||||
|
}
|
||||||
|
export type Endpoint4_22Output = StreamValue<EffectValue<ReturnType<RawClient["server.session"]["session.log"]>>>
|
||||||
|
export type SessionLogOperation<E = never> = (input: Endpoint4_22Input) => Stream.Stream<Endpoint4_22Output, E>
|
||||||
|
|
||||||
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.background"]>[0]
|
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
|
||||||
export type Endpoint4_23Input = { readonly sessionID: Endpoint4_23Request["params"]["sessionID"] }
|
export type Endpoint4_23Input = { readonly sessionID: Endpoint4_23Request["params"]["sessionID"] }
|
||||||
export type Endpoint4_23Output = EffectValue<ReturnType<RawClient["server.session"]["session.background"]>>
|
export type Endpoint4_23Output = EffectValue<ReturnType<RawClient["server.session"]["session.interrupt"]>>
|
||||||
export type SessionBackgroundOperation<E = never> = (input: Endpoint4_23Input) => Effect.Effect<Endpoint4_23Output, E>
|
export type SessionInterruptOperation<E = never> = (input: Endpoint4_23Input) => Effect.Effect<Endpoint4_23Output, E>
|
||||||
|
|
||||||
type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.message"]>[0]
|
type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.background"]>[0]
|
||||||
export type Endpoint4_24Input = {
|
export type Endpoint4_24Input = { readonly sessionID: Endpoint4_24Request["params"]["sessionID"] }
|
||||||
readonly sessionID: Endpoint4_24Request["params"]["sessionID"]
|
export type Endpoint4_24Output = EffectValue<ReturnType<RawClient["server.session"]["session.background"]>>
|
||||||
readonly messageID: Endpoint4_24Request["params"]["messageID"]
|
export type SessionBackgroundOperation<E = never> = (input: Endpoint4_24Input) => Effect.Effect<Endpoint4_24Output, E>
|
||||||
|
|
||||||
|
type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.message"]>[0]
|
||||||
|
export type Endpoint4_25Input = {
|
||||||
|
readonly sessionID: Endpoint4_25Request["params"]["sessionID"]
|
||||||
|
readonly messageID: Endpoint4_25Request["params"]["messageID"]
|
||||||
}
|
}
|
||||||
export type Endpoint4_24Output = EffectValue<ReturnType<RawClient["server.session"]["session.message"]>>["data"]
|
export type Endpoint4_25Output = EffectValue<ReturnType<RawClient["server.session"]["session.message"]>>["data"]
|
||||||
export type SessionMessageOperation<E = never> = (input: Endpoint4_24Input) => Effect.Effect<Endpoint4_24Output, E>
|
export type SessionMessageOperation<E = never> = (input: Endpoint4_25Input) => Effect.Effect<Endpoint4_25Output, E>
|
||||||
|
|
||||||
export interface SessionApi<E = never> {
|
export interface SessionApi<E = never> {
|
||||||
readonly list: SessionListOperation<E>
|
readonly list: SessionListOperation<E>
|
||||||
|
|
@ -257,6 +266,7 @@ export interface SessionApi<E = never> {
|
||||||
readonly command: SessionCommandOperation<E>
|
readonly command: SessionCommandOperation<E>
|
||||||
readonly skill: SessionSkillOperation<E>
|
readonly skill: SessionSkillOperation<E>
|
||||||
readonly synthetic: SessionSyntheticOperation<E>
|
readonly synthetic: SessionSyntheticOperation<E>
|
||||||
|
readonly shell: SessionShellOperation<E>
|
||||||
readonly compact: SessionCompactOperation<E>
|
readonly compact: SessionCompactOperation<E>
|
||||||
readonly wait: SessionWaitOperation<E>
|
readonly wait: SessionWaitOperation<E>
|
||||||
readonly revertStage: SessionRevertStageOperation<E>
|
readonly revertStage: SessionRevertStageOperation<E>
|
||||||
|
|
|
||||||
|
|
@ -349,6 +349,26 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.add(
|
||||||
|
HttpApiEndpoint.post("session.shell", "/api/session/:sessionID/shell", {
|
||||||
|
params: { sessionID: Session.ID },
|
||||||
|
payload: Schema.Struct({
|
||||||
|
id: Event.ID.pipe(Schema.optional),
|
||||||
|
command: Schema.String,
|
||||||
|
}),
|
||||||
|
success: HttpApiSchema.NoContent,
|
||||||
|
error: SessionNotFoundError,
|
||||||
|
})
|
||||||
|
.middleware(sessionLocationMiddleware)
|
||||||
|
.annotateMerge(
|
||||||
|
OpenApi.annotations({
|
||||||
|
identifier: "v2.session.shell",
|
||||||
|
summary: "Run shell command",
|
||||||
|
description:
|
||||||
|
"Execute one shell command in the session's working directory. Emits a shell.started event before execution and a shell.ended event with the merged output after.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
.add(
|
.add(
|
||||||
HttpApiEndpoint.post("session.compact", "/api/session/:sessionID/compact", {
|
HttpApiEndpoint.post("session.compact", "/api/session/:sessionID/compact", {
|
||||||
params: { sessionID: Session.ID },
|
params: { sessionID: Session.ID },
|
||||||
|
|
|
||||||
|
|
@ -422,6 +422,8 @@ import type {
|
||||||
V2SessionRevertCommitResponses,
|
V2SessionRevertCommitResponses,
|
||||||
V2SessionRevertStageErrors,
|
V2SessionRevertStageErrors,
|
||||||
V2SessionRevertStageResponses,
|
V2SessionRevertStageResponses,
|
||||||
|
V2SessionShellErrors,
|
||||||
|
V2SessionShellResponses,
|
||||||
V2SessionSkillErrors,
|
V2SessionSkillErrors,
|
||||||
V2SessionSkillResponses,
|
V2SessionSkillResponses,
|
||||||
V2SessionSwitchAgentErrors,
|
V2SessionSwitchAgentErrors,
|
||||||
|
|
@ -6240,6 +6242,43 @@ export class Session3 extends HeyApiClient {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run shell command
|
||||||
|
*
|
||||||
|
* Execute one shell command in the session's working directory. Emits a shell.started event before execution and a shell.ended event with the merged output after.
|
||||||
|
*/
|
||||||
|
public shell<ThrowOnError extends boolean = false>(
|
||||||
|
parameters: {
|
||||||
|
sessionID: string
|
||||||
|
id?: string | null
|
||||||
|
command?: string
|
||||||
|
},
|
||||||
|
options?: Options<never, ThrowOnError>,
|
||||||
|
) {
|
||||||
|
const params = buildClientParams(
|
||||||
|
[parameters],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
args: [
|
||||||
|
{ in: "path", key: "sessionID" },
|
||||||
|
{ in: "body", key: "id" },
|
||||||
|
{ in: "body", key: "command" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return (options?.client ?? this.client).post<V2SessionShellResponses, V2SessionShellErrors, ThrowOnError>({
|
||||||
|
url: "/api/session/{sessionID}/shell",
|
||||||
|
...options,
|
||||||
|
...params,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...options?.headers,
|
||||||
|
...params.headers,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compact session
|
* Compact session
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -16686,6 +16686,44 @@ export type V2SessionSyntheticResponses = {
|
||||||
|
|
||||||
export type V2SessionSyntheticResponse = V2SessionSyntheticResponses[keyof V2SessionSyntheticResponses]
|
export type V2SessionSyntheticResponse = V2SessionSyntheticResponses[keyof V2SessionSyntheticResponses]
|
||||||
|
|
||||||
|
export type V2SessionShellData = {
|
||||||
|
body: {
|
||||||
|
id?: string | null
|
||||||
|
command: string
|
||||||
|
}
|
||||||
|
path: {
|
||||||
|
sessionID: string
|
||||||
|
}
|
||||||
|
query?: never
|
||||||
|
url: "/api/session/{sessionID}/shell"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type V2SessionShellErrors = {
|
||||||
|
/**
|
||||||
|
* InvalidRequestError
|
||||||
|
*/
|
||||||
|
400: InvalidRequestErrorV2
|
||||||
|
/**
|
||||||
|
* UnauthorizedError
|
||||||
|
*/
|
||||||
|
401: UnauthorizedErrorV2
|
||||||
|
/**
|
||||||
|
* SessionNotFoundError
|
||||||
|
*/
|
||||||
|
404: SessionNotFoundErrorV2
|
||||||
|
}
|
||||||
|
|
||||||
|
export type V2SessionShellError = V2SessionShellErrors[keyof V2SessionShellErrors]
|
||||||
|
|
||||||
|
export type V2SessionShellResponses = {
|
||||||
|
/**
|
||||||
|
* <No Content>
|
||||||
|
*/
|
||||||
|
204: void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type V2SessionShellResponse = V2SessionShellResponses[keyof V2SessionShellResponses]
|
||||||
|
|
||||||
export type V2SessionCompactData = {
|
export type V2SessionCompactData = {
|
||||||
body?: never
|
body?: never
|
||||||
path: {
|
path: {
|
||||||
|
|
|
||||||
|
|
@ -323,6 +323,24 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
||||||
return HttpApiSchema.NoContent.make()
|
return HttpApiSchema.NoContent.make()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.handle(
|
||||||
|
"session.shell",
|
||||||
|
Effect.fn(function* (ctx) {
|
||||||
|
yield* session
|
||||||
|
.shell({ sessionID: ctx.params.sessionID, id: ctx.payload.id, command: ctx.payload.command })
|
||||||
|
.pipe(
|
||||||
|
Effect.catchTag("Session.NotFoundError", (error) =>
|
||||||
|
Effect.fail(
|
||||||
|
new SessionNotFoundError({
|
||||||
|
sessionID: error.sessionID,
|
||||||
|
message: `Session not found: ${error.sessionID}`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return HttpApiSchema.NoContent.make()
|
||||||
|
}),
|
||||||
|
)
|
||||||
.handle(
|
.handle(
|
||||||
"session.compact",
|
"session.compact",
|
||||||
Effect.fn(function* (ctx) {
|
Effect.fn(function* (ctx) {
|
||||||
|
|
|
||||||
|
|
@ -1101,13 +1101,8 @@ export function Prompt(props: PromptProps) {
|
||||||
|
|
||||||
if (store.mode === "shell") {
|
if (store.mode === "shell") {
|
||||||
move.startSubmit()
|
move.startSubmit()
|
||||||
void sdk.client.session.shell({
|
void sdk.client.v2.session.shell({
|
||||||
sessionID,
|
sessionID,
|
||||||
agent: agent.id,
|
|
||||||
model: {
|
|
||||||
providerID: selectedModel.providerID,
|
|
||||||
modelID: selectedModel.modelID,
|
|
||||||
},
|
|
||||||
command: inputText,
|
command: inputText,
|
||||||
})
|
})
|
||||||
setStore("mode", "normal")
|
setStore("mode", "normal")
|
||||||
|
|
|
||||||
|
|
@ -1068,9 +1068,7 @@ function SessionMessageView(props: { message: SessionMessage }) {
|
||||||
<UserMessage message={props.message as SessionMessageUser} />
|
<UserMessage message={props.message as SessionMessageUser} />
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={props.message.type === "shell"}>
|
<Match when={props.message.type === "shell"}>
|
||||||
<box paddingLeft={3}>
|
<ShellMessage message={props.message as Extract<SessionMessage, { type: "shell" }>} />
|
||||||
<text>{props.message.type === "shell" ? `$ ${props.message.command}\n${props.message.output}` : ""}</text>
|
|
||||||
</box>
|
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={props.message.type === "agent-switched" || props.message.type === "model-switched"}>
|
<Match when={props.message.type === "agent-switched" || props.message.type === "model-switched"}>
|
||||||
<SessionSwitchMessageV2 message={props.message} />
|
<SessionSwitchMessageV2 message={props.message} />
|
||||||
|
|
@ -1346,6 +1344,29 @@ function RevertMessage(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ShellMessage(props: { message: Extract<SessionMessage, { type: "shell" }> }) {
|
||||||
|
const { theme } = useTheme()
|
||||||
|
const output = createMemo(() => stripAnsi(props.message.output.trim()))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box
|
||||||
|
border={["left"]}
|
||||||
|
paddingTop={1}
|
||||||
|
paddingBottom={1}
|
||||||
|
paddingLeft={2}
|
||||||
|
gap={1}
|
||||||
|
backgroundColor={theme.backgroundPanel}
|
||||||
|
customBorderChars={SplitBorder.customBorderChars}
|
||||||
|
borderColor={theme.background}
|
||||||
|
>
|
||||||
|
<text fg={theme.text}>$ {props.message.command}</text>
|
||||||
|
<Show when={output()}>
|
||||||
|
<text fg={theme.textMuted}>{output()}</text>
|
||||||
|
</Show>
|
||||||
|
</box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function UserMessage(props: { message: SessionMessageUser }) {
|
function UserMessage(props: { message: SessionMessageUser }) {
|
||||||
const ctx = use()
|
const ctx = use()
|
||||||
const data = useData()
|
const data = useData()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue