feat(server): prompt 支持 parts/agent 传递,import 迁移至子模块

- session route: ssePrompt 支持 parts 参数,支持 agent mentions
- session route: createSession 支持自定义 session_id
- provider/sessionManager: InitData import 从 types.js 统一导入
This commit is contained in:
DoSun 2026-05-11 14:33:31 +08:00
parent 3ebfb2c05d
commit 799ed96b37
3 changed files with 28 additions and 14 deletions

View File

@ -1,6 +1,6 @@
import { Hono } from 'hono' import { Hono } from 'hono'
import type { SessionManager } from '../sessionManager.js' import type { SessionManager } from '../sessionManager.js'
import type { InitData } from '../sessionHandle.js' import type { InitData } from '../types.js'
type ModelInfo = { type ModelInfo = {
value: string value: string

View File

@ -1,7 +1,7 @@
import { Hono } from 'hono' import { Hono } from 'hono'
import { streamSSE } from 'hono/streaming' import { streamSSE } from 'hono/streaming'
import type { SessionManager } from '../sessionManager.js' import type { SessionManager } from '../sessionManager.js'
import { getScriptArgsForChild } from '../sessionHandle.js' import { getScriptArgsForChild } from '../childSpawn.js'
import { import {
badRequest, badRequest,
notFound, notFound,
@ -26,6 +26,7 @@ function ssePrompt(
id: string, id: string,
content: string, content: string,
c: import('hono').Context, c: import('hono').Context,
parts?: Array<Record<string, unknown>>,
) { ) {
return streamSSE(c, async stream => { return streamSSE(c, async stream => {
const startTime = Date.now() const startTime = Date.now()
@ -81,7 +82,7 @@ function ssePrompt(
}) })
try { try {
await handle.prompt(content) await handle.prompt(content, { parts })
} catch { } catch {
if (!resultWritten) { if (!resultWritten) {
pendingWrite = pendingWrite.then(() => pendingWrite = pendingWrite.then(() =>
@ -123,6 +124,7 @@ export function createSessionRoutes(
.post('/session', async c => { .post('/session', async c => {
const body = await c.req.json<{ const body = await c.req.json<{
cwd?: string cwd?: string
session_id?: string
permission_mode?: string permission_mode?: string
permission?: Array<{ permission: string; pattern: string; action: string }> permission?: Array<{ permission: string; pattern: string; action: string }>
model?: string model?: string
@ -151,6 +153,7 @@ export function createSessionRoutes(
try { try {
const handle = await sessionManager.createSession({ const handle = await sessionManager.createSession({
cwd, cwd,
sessionId: body.session_id,
model: body.model, model: body.model,
permissionMode, permissionMode,
systemPrompt: body.system_prompt, systemPrompt: body.system_prompt,
@ -365,16 +368,21 @@ export function createSessionRoutes(
const body = await c.req.json<{ const body = await c.req.json<{
content?: string content?: string
parts?: Array<{ type: string; text?: string }> parts?: Array<Record<string, unknown>>
files?: string[] files?: string[]
images?: unknown[] images?: unknown[]
model?: { providerID?: string; modelID?: string } model?: { providerID?: string; modelID?: string }
}>() }>()
const content = body.content ?? body.parts const textContent = body.content ?? body.parts
?.filter((p) => p.type === 'text' && p.text) ?.filter((p): p is Record<string, unknown> & { type: string; text?: string } => p.type === 'text' && !!(p as { text?: string }).text)
.map((p) => p.text) .map((p) => (p as { text: string }).text)
.join('\n') ?? '' .join('\n') ?? ''
const agentParts = body.parts?.filter((p) => p.type === 'agent' && p.name) ?? []
const agentMentions = agentParts.map((p) => `@agent-${p.name}`).join(' ')
const content = [textContent, agentMentions].filter(Boolean).join('\n')
if (!content) throw badRequest('content is required') if (!content) throw badRequest('content is required')
if (handle.prompting) throw conflict('session is already processing a prompt') if (handle.prompting) throw conflict('session is already processing a prompt')
@ -382,7 +390,7 @@ export function createSessionRoutes(
try { await handle.setModel(body.model.modelID) } catch {} try { await handle.setModel(body.model.modelID) } catch {}
} }
return ssePrompt(handle, id, content, c) return ssePrompt(handle, id, content, c, body.parts)
}) })
.post('/session/:sessionID/prompt_async', async c => { .post('/session/:sessionID/prompt_async', async c => {
const id = c.req.param('sessionID') const id = c.req.param('sessionID')
@ -408,16 +416,21 @@ export function createSessionRoutes(
const body = await c.req.json<{ const body = await c.req.json<{
content?: string content?: string
parts?: Array<{ type: string; text?: string }> parts?: Array<Record<string, unknown>>
files?: string[] files?: string[]
images?: unknown[] images?: unknown[]
model?: { providerID?: string; modelID?: string } model?: { providerID?: string; modelID?: string }
}>() }>()
const content = body.content ?? body.parts const textContent = body.content ?? body.parts
?.filter((p) => p.type === 'text' && p.text) ?.filter((p): p is Record<string, unknown> & { type: string; text?: string } => p.type === 'text' && !!(p as { text?: string }).text)
.map((p) => p.text) .map((p) => (p as { text: string }).text)
.join('\n') ?? '' .join('\n') ?? ''
const agentParts = body.parts?.filter((p) => p.type === 'agent' && p.name) ?? []
const agentMentions = agentParts.map((p) => `@agent-${p.name}`).join(' ')
const content = [textContent, agentMentions].filter(Boolean).join('\n')
if (!content) { if (!content) {
throw badRequest('content is required') throw badRequest('content is required')
} }
@ -433,7 +446,7 @@ export function createSessionRoutes(
if (body.model?.modelID) { if (body.model?.modelID) {
try { await handle.setModel(body.model.modelID) } catch {} try { await handle.setModel(body.model.modelID) } catch {}
} }
handle.prompt(content).catch(() => {}) handle.prompt(content, { parts: body.parts }).catch(() => {})
} catch {} } catch {}
})() })()

View File

@ -4,7 +4,8 @@ import { join, resolve, isAbsolute } from 'path'
import { getClaudeConfigHomeDir } from '../utils/envUtils.js' import { getClaudeConfigHomeDir } from '../utils/envUtils.js'
import { logError } from '../utils/log.js' import { logError } from '../utils/log.js'
import type { EventBus } from './eventBus.js' import type { EventBus } from './eventBus.js'
import { SessionHandle, type InitData } from './sessionHandle.js' import { SessionHandle } from './sessionHandle.js'
import type { InitData } from './types.js'
import type { SessionIndex, SessionIndexEntry, SessionBusyStatus, SessionState } from './types.js' import type { SessionIndex, SessionIndexEntry, SessionBusyStatus, SessionState } from './types.js'
import { canonicalizePath } from '../utils/sessionStoragePortable.js' import { canonicalizePath } from '../utils/sessionStoragePortable.js'