refactor(session): fold the session family into packages/session/
git mv the 12 packages from session-persistence/, session-projection/, session-title/, and telemetry/ into one session/ group per the regrouping RFC; merge the four group READMEs into one bilingual triplet; rewrite the group segment in tsconfig references (intra-group references shorten to ../<pkg>), tsconfig.base.json paths/globs, knip.json keys, vitest include, gate scripts, and authored doc/note citations; regenerate module graph, doc graphs, catalogs, and the lockfile importer keys. No npm names change. Full unit suite: 8779 passed; the 18 reported failures reproduce as env flakes (ambient-proxy IPv6 tunneling, watched-dir inotify timeouts under parallel load) — each passes in isolation with NO_PROXY set, matching their known pre-existing behavior on master.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import Loader from '@cordisjs/plugin-loader'
|
||||
import Include from '@cordisjs/plugin-include'
|
||||
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import LlmService, { createUserMessage, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import SessionTitleService from '@deepseek-ai/dsh-session-title'
|
||||
import * as providerPlugin from '@deepseek-ai/dsh-session-title-first-message-llm'
|
||||
|
||||
let root: string | undefined
|
||||
let context: Context | undefined
|
||||
|
||||
class LoaderAdapter extends LlmAdapter {
|
||||
readonly requests: GenerateOptions[] = []
|
||||
|
||||
override async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
|
||||
this.requests.push(options)
|
||||
yield { type: 'text-delta', index: 0, text: 'Loader composed title' }
|
||||
yield { type: 'finish', reason: { kind: 'stop' } }
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await context?.fiber.dispose()
|
||||
context = undefined
|
||||
if (root !== undefined) await rm(root, { recursive: true, force: true })
|
||||
root = undefined
|
||||
})
|
||||
|
||||
async function loadComposition(): Promise<Context> {
|
||||
root = await mkdtemp(join(tmpdir(), 'dsh-title-loader-'))
|
||||
const configPath = join(root, 'cordis.yml')
|
||||
await writeFile(configPath, [
|
||||
"- name: '@deepseek-ai/dsh-llm'",
|
||||
"- name: '@deepseek-ai/dsh-session'",
|
||||
"- name: '@deepseek-ai/dsh-session-title'",
|
||||
' config:',
|
||||
' fallbackMaxWords: 5',
|
||||
' fallbackMaxBytes: 40',
|
||||
' maxTitleBytes: 80',
|
||||
"- name: '@deepseek-ai/dsh-session-title-first-message-llm'",
|
||||
' config:',
|
||||
' targetWords: 5',
|
||||
' targetCjkCharacters: 10',
|
||||
' maxInputBytes: 1000',
|
||||
' maxOutputTokens: 32',
|
||||
' timeoutMs: 1000',
|
||||
" provider: 'title-route'",
|
||||
" model: 'title-model'",
|
||||
'',
|
||||
].join('\n'))
|
||||
|
||||
context = new Context()
|
||||
context.baseUrl = pathToFileURL(root).href + '/'
|
||||
await context.plugin(Loader)
|
||||
context.loader.builtins.include = Include
|
||||
const modules = new Map<string, unknown>([
|
||||
['@deepseek-ai/dsh-llm', LlmService],
|
||||
['@deepseek-ai/dsh-session', SessionStore],
|
||||
['@deepseek-ai/dsh-session-title', SessionTitleService],
|
||||
['@deepseek-ai/dsh-session-title-first-message-llm', providerPlugin],
|
||||
])
|
||||
context.loader.internal = {
|
||||
version: 'v2',
|
||||
async import(specifier: string) {
|
||||
if (!modules.has(specifier)) throw new Error(`unexpected Loader import: ${specifier}`)
|
||||
return modules.get(specifier)
|
||||
},
|
||||
} as unknown as NonNullable<typeof context.loader.internal>
|
||||
await context.loader.create({
|
||||
name: 'cordis:include',
|
||||
config: { path: pathToFileURL(configPath).href },
|
||||
})
|
||||
await context.loader.await()
|
||||
return context
|
||||
}
|
||||
|
||||
describe('session-title Loader composition', () => {
|
||||
it('loads the service and one model provider with required deployment policy', async () => {
|
||||
const ctx = await loadComposition()
|
||||
const unloaded = [...ctx.loader.entries()]
|
||||
.filter(entry => entry.fiber === undefined && !entry.disabled)
|
||||
.map(entry => entry.options.name)
|
||||
expect(unloaded).toEqual([])
|
||||
|
||||
const adapter = new LoaderAdapter()
|
||||
ctx.llm.registerAdapter(['title-route'], adapter)
|
||||
const session = ctx.sessions.create(SessionId('loader-title'))
|
||||
session.append('turn/start', {
|
||||
turn: 1,
|
||||
})
|
||||
const message = session.append('user/message', createUserMessage({
|
||||
content: [{ type: 'text', text: 'Compose a title through Loader' }],
|
||||
source: { kind: 'user' },
|
||||
}), { surfaceOp: 'append' })
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
session.append('request/header', {
|
||||
header: { config: { provider: 'main-route', model: 'main-model' } },
|
||||
reason: 'initial',
|
||||
})
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
|
||||
expect(adapter.requests[0]).toMatchObject({ provider: 'title-route', model: 'title-model' })
|
||||
expect(ctx.sessionTitle.get(session)).toMatchObject({
|
||||
title: 'Loader composed title',
|
||||
messageSeqs: [message.seq],
|
||||
source: {
|
||||
kind: 'provider',
|
||||
provider: 'session-title-first-message-llm',
|
||||
model: { provider: 'title-route', model: 'title-model' },
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user