style(session): simplify prompt parts names

This commit is contained in:
Aiden Cline
2026-05-19 09:41:43 -05:00
parent 1c8f84c4be
commit 2d900e8c86
2 changed files with 54 additions and 65 deletions
+10 -10
View File
@@ -114,11 +114,11 @@ export const layer = Layer.effect(
const references = yield* Reference.Service
const events = yield* EventV2Bridge.Service
const flags = yield* RuntimeFlags.Service
const promptPartServices = { agents, bus, fsys, image, lsp, mcp, plugin, provider, references, registry }
const services = { agents, bus, fsys, image, lsp, mcp, plugin, provider, references, registry }
const ops = Effect.fn("SessionPrompt.ops")(function* () {
return {
cancel: (sessionID: SessionID) => cancel(sessionID),
resolvePromptParts: (template: string) => SessionPromptParts.resolvePromptParts(template, promptPartServices),
resolvePromptParts: (template: string) => SessionPromptParts.resolvePromptParts(template, services),
prompt: (input: PromptInput) => prompt(input).pipe(Effect.catch(Effect.die)),
loop: (input: LoopInput) => loop(input),
} satisfies TaskPromptOps
@@ -649,7 +649,7 @@ export const layer = Layer.effect(
}
yield* Effect.addFinalizer(() => instruction.clear(info.id))
const resolved = yield* SessionPromptParts.resolveMessageParts({ input, info, agent: ag }, promptPartServices)
const resolved = yield* SessionPromptParts.resolveMessageParts({ prompt: input, info, agent: ag }, services)
yield* sessions.updateMessage(info)
for (const part of resolved.parts) yield* sessions.updatePart(part)
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
@@ -658,14 +658,14 @@ export const layer = Layer.effect(
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(info.time.created),
prompt: {
text: resolved.prompt.text.join("\n"),
files: resolved.prompt.files,
agents: resolved.prompt.agents,
references: resolved.prompt.references,
text: resolved.nextPrompt.text.join("\n"),
files: resolved.nextPrompt.files,
agents: resolved.nextPrompt.agents,
references: resolved.nextPrompt.references,
},
})
}
for (const text of resolved.prompt.synthetic) {
for (const text of resolved.nextPrompt.synthetic) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Synthetic, {
@@ -1038,7 +1038,7 @@ export const layer = Layer.effect(
throw error
}
const templateParts = yield* SessionPromptParts.resolvePromptParts(template, promptPartServices)
const templateParts = yield* SessionPromptParts.resolvePromptParts(template, services)
const isSubtask = (agent.mode === "subagent" && cmd.subtask !== false) || cmd.subtask === true
const parts = isSubtask
? [
@@ -1089,7 +1089,7 @@ export const layer = Layer.effect(
loop,
shell,
command,
resolvePromptParts: (template) => SessionPromptParts.resolvePromptParts(template, promptPartServices),
resolvePromptParts: (template) => SessionPromptParts.resolvePromptParts(template, services),
})
}),
)
+44 -55
View File
@@ -30,44 +30,33 @@ const log = Log.create({ service: "session.prompt.parts" })
const decodeMessageInfo = Schema.decodeUnknownExit(MessageV2.Info)
const decodeMessagePart = Schema.decodeUnknownExit(MessageV2.Part)
export type ResolvedPrompt = {
text: string[]
files: FileAttachment[]
agents: AgentAttachment[]
references: ReferenceAttachment[]
synthetic: string[]
}
type ResolvePromptPartsServices = {
type Services = {
agents: Agent.Interface
fsys: AppFileSystem.Interface
references: Reference.Interface
}
type ResolveMessagePartsServices = ResolvePromptPartsServices & {
bus: Bus.Interface
fsys: AppFileSystem.Interface
image: Image.Interface
lsp: LSP.Interface
mcp: MCP.Interface
plugin: Plugin.Interface
provider: Provider.Interface
references: Reference.Interface
registry: ToolRegistry.Interface
}
export const resolvePromptParts = Effect.fn("SessionPromptParts.resolvePromptParts")(function* (
template: string,
services: ResolvePromptPartsServices,
services: Services,
) {
return yield* resolveTemplateParts(template, services)
})
export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageParts")(function* (
input: {
input: PromptInput
prompt: PromptInput
info: MessageV2.User
agent: Agent.Info
},
services: ResolveMessagePartsServices,
services: Services,
) {
type Draft<T> = T extends MessageV2.Part ? Omit<T, "id"> & { id?: string } : never
const assign = (part: Draft<MessageV2.Part>): MessageV2.Part => ({
@@ -109,7 +98,7 @@ export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageP
const pieces: Draft<MessageV2.Part>[] = [
{
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "text",
synthetic: true,
text: `Reading MCP resource: ${part.filename} (${uri})`,
@@ -124,7 +113,7 @@ export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageP
if ("text" in c && c.text) {
pieces.push({
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "text",
synthetic: true,
text: c.text,
@@ -133,21 +122,21 @@ export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageP
const mime = "mimeType" in c ? c.mimeType : part.mime
pieces.push({
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "text",
synthetic: true,
text: `[Binary content: ${mime}]`,
})
}
}
pieces.push({ ...part, messageID: input.info.id, sessionID: input.input.sessionID })
pieces.push({ ...part, messageID: input.info.id, sessionID: input.prompt.sessionID })
} else {
const error = Cause.squash(exit.cause)
log.error("failed to read MCP resource", { error, clientName, uri })
const message = error instanceof Error ? error.message : String(error)
pieces.push({
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "text",
synthetic: true,
text: `Failed to read MCP resource ${part.filename}: ${message}`,
@@ -162,19 +151,19 @@ export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageP
return [
{
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "text",
synthetic: true,
text: `Called the Read tool with the following input: ${JSON.stringify({ filePath: part.filename })}`,
},
{
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "text",
synthetic: true,
text: decodeDataUrl(part.url),
},
{ ...part, messageID: input.info.id, sessionID: input.input.sessionID },
{ ...part, messageID: input.info.id, sessionID: input.prompt.sessionID },
]
}
break
@@ -189,7 +178,7 @@ export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageP
const controller = new AbortController()
return read
.execute(args, {
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
abort: controller.signal,
agent: input.info.agent,
messageID: input.info.id,
@@ -230,11 +219,11 @@ export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageP
const args = { filePath: filepath, offset, limit }
const pieces: Draft<MessageV2.Part>[] = [
...(referenceContext
? [{ ...referenceContext, messageID: input.info.id, sessionID: input.input.sessionID }]
? [{ ...referenceContext, messageID: input.info.id, sessionID: input.prompt.sessionID }]
: []),
{
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "text",
synthetic: true,
text: `Called the Read tool with the following input: ${JSON.stringify(args)}`,
@@ -248,7 +237,7 @@ export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageP
const result = exit.value
pieces.push({
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "text",
synthetic: true,
text: result.output,
@@ -260,23 +249,23 @@ export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageP
synthetic: true,
filename: a.filename ?? part.filename,
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
})),
)
} else {
pieces.push({ ...part, mime, messageID: input.info.id, sessionID: input.input.sessionID })
pieces.push({ ...part, mime, messageID: input.info.id, sessionID: input.prompt.sessionID })
}
} else {
const error = Cause.squash(exit.cause)
log.error("failed to read file", { error })
const message = error instanceof Error ? error.message : String(error)
yield* services.bus.publish(Session.Event.Error, {
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
error: new NamedError.Unknown({ message }).toObject(),
})
pieces.push({
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "text",
synthetic: true,
text: `Read tool failed to read ${filepath} with the following error: ${message}`,
@@ -293,16 +282,16 @@ export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageP
log.error("failed to read directory", { error })
const message = error instanceof Error ? error.message : String(error)
yield* services.bus.publish(Session.Event.Error, {
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
error: new NamedError.Unknown({ message }).toObject(),
})
return [
...(referenceContext
? [{ ...referenceContext, messageID: input.info.id, sessionID: input.input.sessionID }]
? [{ ...referenceContext, messageID: input.info.id, sessionID: input.prompt.sessionID }]
: []),
{
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "text",
synthetic: true,
text: `Read tool failed to read ${filepath} with the following error: ${message}`,
@@ -311,33 +300,33 @@ export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageP
}
return [
...(referenceContext
? [{ ...referenceContext, messageID: input.info.id, sessionID: input.input.sessionID }]
? [{ ...referenceContext, messageID: input.info.id, sessionID: input.prompt.sessionID }]
: []),
{
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "text",
synthetic: true,
text: `Called the Read tool with the following input: ${JSON.stringify(args)}`,
},
{
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "text",
synthetic: true,
text: exit.value.output,
},
{ ...part, mime, messageID: input.info.id, sessionID: input.input.sessionID },
{ ...part, mime, messageID: input.info.id, sessionID: input.prompt.sessionID },
]
}
return [
...(referenceContext
? [{ ...referenceContext, messageID: input.info.id, sessionID: input.input.sessionID }]
? [{ ...referenceContext, messageID: input.info.id, sessionID: input.prompt.sessionID }]
: []),
{
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "text",
synthetic: true,
text: `Called the Read tool with the following input: {"filePath":"${filepath}"}`,
@@ -345,7 +334,7 @@ export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageP
{
id: part.id,
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "file",
url:
`data:${mime};base64,` +
@@ -363,10 +352,10 @@ export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageP
const perm = Permission.evaluate("task", part.name, input.agent.permission)
const hint = perm.action === "deny" ? " . Invoked by user; guaranteed to exist." : ""
return [
{ ...part, messageID: input.info.id, sessionID: input.input.sessionID },
{ ...part, messageID: input.info.id, sessionID: input.prompt.sessionID },
{
messageID: input.info.id,
sessionID: input.input.sessionID,
sessionID: input.prompt.sessionID,
type: "text",
synthetic: true,
text:
@@ -377,21 +366,21 @@ export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageP
]
}
return [{ ...part, messageID: input.info.id, sessionID: input.input.sessionID }]
return [{ ...part, messageID: input.info.id, sessionID: input.prompt.sessionID }]
})
const resolvedParts = yield* Effect.forEach(input.input.parts, resolvePart, { concurrency: "unbounded" }).pipe(
const resolvedParts = yield* Effect.forEach(input.prompt.parts, resolvePart, { concurrency: "unbounded" }).pipe(
Effect.map((x) => x.flat().map(assign)),
)
yield* services.plugin.trigger(
"chat.message",
{
sessionID: input.input.sessionID,
agent: input.input.agent,
model: input.input.model,
messageID: input.input.messageID,
variant: input.input.variant,
sessionID: input.prompt.sessionID,
agent: input.prompt.agent,
model: input.prompt.model,
messageID: input.prompt.messageID,
variant: input.prompt.variant,
},
{ message: input.info, parts: resolvedParts },
)
@@ -408,10 +397,10 @@ export const resolveMessageParts = Effect.fn("SessionPromptParts.resolveMessageP
)
validate(input.info, parts)
return { parts, prompt: promptPayload(parts) }
return { parts, nextPrompt: nextPrompt(parts) }
})
function resolveTemplateParts(template: string, services: ResolvePromptPartsServices) {
function resolveTemplateParts(template: string, services: Services) {
return Effect.gen(function* () {
const ctx = yield* InstanceState.context
const parts: Types.DeepMutable<PromptInput["parts"]> = [{ type: "text", text: template }]
@@ -535,7 +524,7 @@ function validate(info: MessageV2.User, parts: MessageV2.Part[]) {
})
}
function promptPayload(parts: MessageV2.Part[]): ResolvedPrompt {
function nextPrompt(parts: MessageV2.Part[]) {
return parts.reduce(
(result, part) => {
if (part.type === "text") {