@lythos/agent-adapter-deepseek-serve 0.9.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@lythos/agent-adapter-deepseek-serve",
3
+ "version": "0.9.33",
4
+ "type": "module",
5
+ "main": "./src/index.ts",
6
+ "exports": {
7
+ ".": "./src/index.ts"
8
+ },
9
+ "license": "MIT",
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "keywords": [
14
+ "deepseek",
15
+ "agent-adapter",
16
+ "deepseek-tui",
17
+ "serve-mode"
18
+ ],
19
+ "dependencies": {
20
+ "@lythos/agent-adapter": "^0.9.33"
21
+ }
22
+ }
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Actor FSM tests: lock file, PID detection, version parsing, session IDs.
3
+ * No real serve process needed — pure state machine + injectable fs.
4
+ */
5
+
6
+ import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
7
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
8
+ import { tmpdir } from 'node:os'
9
+ import { join } from 'node:path'
10
+
11
+ // Re-import the module for each test to get a fresh state
12
+ // We test the exported adapter + internal pure functions indirectly
13
+
14
+ // ── Helpers ──────────────────────────────────────────────────────────────────
15
+
16
+ function tempLockDir() {
17
+ const dir = mkdtempSync(join(tmpdir(), 'deepseek-test-'))
18
+ return dir
19
+ }
20
+
21
+ function writeTestLock(dir: string, lock: Record<string, unknown>) {
22
+ writeFileSync(join(dir, 'deepseek-serve.json'), JSON.stringify(lock, null, 2))
23
+ }
24
+
25
+ // ── Lock file FSM ───────────────────────────────────────────────────────────
26
+
27
+ describe('ServeLock FSM — lock file lifecycle', () => {
28
+ let lockDir: string
29
+
30
+ beforeEach(() => { lockDir = tempLockDir() })
31
+ afterEach(() => { try { rmSync(lockDir, { recursive: true, force: true }) } catch {} })
32
+
33
+ test('FSM: no lock → null', () => {
34
+ const lockPath = join(lockDir, 'deepseek-serve.json')
35
+ // inline logic
36
+ // Simulate readLock logic
37
+ expect(existsSync(lockPath)).toBe(false)
38
+ })
39
+
40
+ test('FSM: write lock → read back', () => {
41
+ const lock = { pid: 12345, port: 17878, version: '0.8.14', startedAt: new Date().toISOString(), threads: {} }
42
+ writeTestLock(lockDir, lock)
43
+ const content = readFileSync(join(lockDir, 'deepseek-serve.json'), 'utf-8')
44
+ const parsed = JSON.parse(content)
45
+ expect(parsed.pid).toBe(12345)
46
+ expect(parsed.port).toBe(17878)
47
+ expect(parsed.version).toBe('0.8.14')
48
+ expect(parsed.threads).toEqual({})
49
+ })
50
+
51
+ test('FSM: corrupt lock → null (not crash)', () => {
52
+ writeFileSync(join(lockDir, 'deepseek-serve.json'), 'not valid json {{{')
53
+ // readLock should return null, not throw
54
+ try {
55
+ const raw = readFileSync(join(lockDir, 'deepseek-serve.json'), 'utf-8')
56
+ const parsed = JSON.parse(raw)
57
+ // Should have thrown
58
+ expect(false).toBe(true)
59
+ } catch {
60
+ // Expected — JSON parse fails
61
+ expect(true).toBe(true)
62
+ }
63
+ })
64
+
65
+ test('FSM: updateThreadMapping adds entry', () => {
66
+ const lock = { pid: 12345, port: 17878, version: '0.8.14', startedAt: new Date().toISOString(), threads: {} as Record<string, string> }
67
+ writeTestLock(lockDir, lock)
68
+ // Simulate update
69
+ lock.threads['arena-20260508-001'] = 'thr_abc123'
70
+ writeTestLock(lockDir, lock)
71
+ const content = readFileSync(join(lockDir, 'deepseek-serve.json'), 'utf-8')
72
+ const parsed = JSON.parse(content)
73
+ expect(parsed.threads['arena-20260508-001']).toBe('thr_abc123')
74
+ expect(Object.keys(parsed.threads).length).toBe(1)
75
+ })
76
+
77
+ test('FSM: multiple sessions map to different threads', () => {
78
+ const lock = { pid: 12345, port: 17878, version: '0.8.14', startedAt: new Date().toISOString(), threads: {} as Record<string, string> }
79
+ lock.threads['arena-20260508-001'] = 'thr_aaa'
80
+ lock.threads['arena-20260508-002'] = 'thr_bbb'
81
+ lock.threads['arena-20260508-003'] = 'thr_ccc'
82
+ writeTestLock(lockDir, lock)
83
+ const content = readFileSync(join(lockDir, 'deepseek-serve.json'), 'utf-8')
84
+ const parsed = JSON.parse(content)
85
+ expect(Object.keys(parsed.threads).length).toBe(3)
86
+ expect(parsed.threads['arena-20260508-002']).toBe('thr_bbb')
87
+ })
88
+ })
89
+
90
+ // ── PID detection ───────────────────────────────────────────────────────────
91
+
92
+ describe('isProcessAlive — PID signal', () => {
93
+ test('current process PID is alive', () => {
94
+ const alive = (() => {
95
+ try { process.kill(process.pid, 0); return true } catch { return false }
96
+ })()
97
+ expect(alive).toBe(true)
98
+ })
99
+
100
+ test('impossible PID is dead', () => {
101
+ // PID 99999 is extremely unlikely to exist
102
+ const alive = (() => {
103
+ try { process.kill(99999, 0); return true } catch { return false }
104
+ })()
105
+ expect(alive).toBe(false)
106
+ })
107
+
108
+ test('PID 0 is alive (self)', () => {
109
+ // process.kill(0, 0) signals the whole process group — always succeeds
110
+ const alive = (() => {
111
+ try { process.kill(0, 0); return true } catch { return false }
112
+ })()
113
+ expect(alive).toBe(true)
114
+ })
115
+ })
116
+
117
+ // ── Version parsing ─────────────────────────────────────────────────────────
118
+
119
+ describe('getVersion — parse deepseek --version', () => {
120
+ test('parses standard version format', () => {
121
+ const out = 'deepseek 0.8.14\n'
122
+ const match = out.match(/(\d+\.\d+\.\d+)/)
123
+ expect(match?.[1]).toBe('0.8.14')
124
+ })
125
+
126
+ test('parses version from mixed output', () => {
127
+ const out = 'DeepSeek TUI v0.8.14 (abc1234)\nRuntime: Rust 1.80\n'
128
+ const match = out.match(/(\d+\.\d+\.\d+)/)
129
+ expect(match?.[1]).toBe('0.8.14')
130
+ })
131
+
132
+ test('returns null for no version', () => {
133
+ const out = 'command not found\n'
134
+ const match = out.match(/(\d+\.\d+\.\d+)/)
135
+ expect(match).toBe(null)
136
+ })
137
+
138
+ test('0.8.x passes version check', () => {
139
+ const version = '0.8.14'
140
+ expect(version.startsWith('0.8.')).toBe(true)
141
+ })
142
+
143
+ test('0.9.x still works (with warning)', () => {
144
+ const version = '0.9.0'
145
+ expect(version.startsWith('0.8.')).toBe(false)
146
+ // Should warn but continue
147
+ })
148
+ })
149
+
150
+ // ── Session ID format ───────────────────────────────────────────────────────
151
+
152
+ describe('nextSessionId — format', () => {
153
+ test('starts with arena- prefix', () => {
154
+ let counter = 0
155
+ const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 15)
156
+ counter++
157
+ const id = `arena-${ts}-${String(counter).padStart(3, '0')}`
158
+ expect(id.startsWith('arena-')).toBe(true)
159
+ })
160
+
161
+ test('counter increments', () => {
162
+ let counter = 0
163
+ const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 15)
164
+ const ids: string[] = []
165
+ for (let i = 0; i < 5; i++) {
166
+ counter++
167
+ ids.push(`arena-${ts}-${String(counter).padStart(3, '0')}`)
168
+ }
169
+ expect(ids[0]).toContain('-001')
170
+ expect(ids[4]).toContain('-005')
171
+ // All unique
172
+ expect(new Set(ids).size).toBe(5)
173
+ })
174
+ })
175
+
176
+ // ── Adapter registration ────────────────────────────────────────────────────
177
+
178
+ describe('adapter registration', () => {
179
+ test('adapter is registered under name "deepseek"', async () => {
180
+ const { deepseekServeAdapter } = await import('./deepseek-serve')
181
+ expect(deepseekServeAdapter.name).toBe('deepseek')
182
+ })
183
+
184
+ test('adapter has spawn method', async () => {
185
+ const { deepseekServeAdapter } = await import('./deepseek-serve')
186
+ expect(typeof deepseekServeAdapter.spawn).toBe('function')
187
+ })
188
+ })
189
+
190
+ // ── Actor FSM: state transitions (conceptual) ───────────────────────────────
191
+
192
+ describe('Actor FSM — state transitions', () => {
193
+ test('state: cold start → serve running', () => {
194
+ // Given: no lock file, serve not running
195
+ // When: ensureServeRunning()
196
+ // Then: findFreePort → spawn serve → health check → writeLock → return port
197
+ // (tested via logic verification only — real serve needed for integration)
198
+ const states = ['no_lock', 'starting', 'health_check', 'ready', 'error']
199
+ expect(states).toContain('ready')
200
+ expect(states).toContain('error')
201
+ })
202
+
203
+ test('state: warm reuse → skip start', () => {
204
+ // Given: lock exists, PID alive, health check passes
205
+ // When: ensureServeRunning()
206
+ // Then: cachedPort → health check → return (no new process)
207
+ const expectedPath = ['check_cache', 'health_pass', 'return_port']
208
+ expect(expectedPath.length).toBe(3)
209
+ })
210
+
211
+ test('state: dead PID → restart', () => {
212
+ // Given: lock exists, PID dead
213
+ // When: ensureServeRunning()
214
+ // Then: isProcessAlive → false → findFreePort → spawn → health → writeLock
215
+ const expectedPath = ['check_lock', 'pid_dead', 'find_port', 'spawn', 'health', 'write_lock', 'ready']
216
+ expect(expectedPath.length).toBe(7)
217
+ })
218
+
219
+ test('state: port occupied → increment', () => {
220
+ // Given: base port 17878 occupied
221
+ // When: findFreePort(17878)
222
+ // Then: try 17879, 17880... until free
223
+ const findNext = (start: number) => start + 1
224
+ expect(findNext(17878)).toBe(17879)
225
+ expect(findNext(17879)).toBe(17880)
226
+ })
227
+ })
228
+
229
+ // ── Thread API paths (conceptual) ──────────────────────────────────────────
230
+
231
+ describe('Thread API — request paths', () => {
232
+ const PORT = 17878
233
+ const THREAD_ID = 'thr_test123'
234
+
235
+ test('POST /v1/threads — create thread', () => {
236
+ const path = `/v1/threads`
237
+ const url = `http://127.0.0.1:${PORT}${path}`
238
+ expect(path).toBe('/v1/threads')
239
+ expect(url).toContain(':17878')
240
+ })
241
+
242
+ test('POST /v1/threads/{id}/turns — send turn', () => {
243
+ const path = `/v1/threads/${THREAD_ID}/turns`
244
+ expect(path).toContain(THREAD_ID)
245
+ expect(path).toContain('/turns')
246
+ })
247
+
248
+ test('GET /v1/threads/{id}/events — SSE stream', () => {
249
+ const path = `/v1/threads/${THREAD_ID}/events`
250
+ const url = `http://127.0.0.1:${PORT}${path}?since_seq=0`
251
+ expect(url).toContain('since_seq=0')
252
+ })
253
+
254
+ test('GET /health — health check', () => {
255
+ const url = `http://127.0.0.1:${PORT}/health`
256
+ expect(url.endsWith('/health')).toBe(true)
257
+ })
258
+ })
259
+
260
+ // ── Lock file schema ────────────────────────────────────────────────────────
261
+
262
+ describe('ServeLock schema', () => {
263
+ test('valid lock matches expected shape', () => {
264
+ const lock = {
265
+ pid: 12345,
266
+ port: 17878,
267
+ version: '0.8.14',
268
+ startedAt: '2026-05-08T05:04:46.734Z',
269
+ threads: { 'arena-001': 'thr_abc' },
270
+ }
271
+ expect(typeof lock.pid).toBe('number')
272
+ expect(typeof lock.port).toBe('number')
273
+ expect(typeof lock.version).toBe('string')
274
+ expect(typeof lock.startedAt).toBe('string')
275
+ expect(typeof lock.threads).toBe('object')
276
+ })
277
+
278
+ test('threads must be string→string map', () => {
279
+ const threads: Record<string, string> = {}
280
+ threads['session-a'] = 'thr_111'
281
+ threads['session-b'] = 'thr_222'
282
+ for (const [k, v] of Object.entries(threads)) {
283
+ expect(typeof k).toBe('string')
284
+ expect(typeof v).toBe('string')
285
+ expect(v.startsWith('thr_')).toBe(true)
286
+ }
287
+ })
288
+ })
@@ -0,0 +1,280 @@
1
+ /**
2
+ * DeepSeek serve-mode adapter — HTTP thread API for full agent execution.
3
+ *
4
+ * Background: `deepseek "prompt"` and `deepseek exec` are text-only (no tool
5
+ * execution). The `deepseek serve --http` thread API supports full agent mode
6
+ * with file ops, shell, web search, and subagents.
7
+ *
8
+ * Lifecycle:
9
+ * 1. Version check — must be in known range (0.8.x)
10
+ * 2. Lock file — ~/.agents/lythoskill/deepseek-serve.json (pid, port, version)
11
+ * 3. If serve running (PID alive) → reuse port
12
+ * 4. If not → start `deepseek serve --http --port <auto>` → write lock
13
+ * 5. HTTP thread API: create thread → send turn → collect SSE → return
14
+ *
15
+ * Per wiki: cortex/wiki/03-lessons/2026-05-06-deepseek-tui-headless-programmatic-analysis.md
16
+ */
17
+
18
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
19
+ import { homedir } from 'node:os'
20
+ import { join } from 'node:path'
21
+ import { spawn, type Subprocess } from 'bun'
22
+ import type { AgentAdapter, AgentRunResult } from '@lythos/agent-adapter'
23
+ import { registerAgent } from '@lythos/agent-adapter'
24
+
25
+ // ── Config ──────────────────────────────────────────────────────────────────
26
+
27
+ const LOCK_DIR = join(homedir(), '.agents', 'lythoskill')
28
+ const LOCK_FILE = join(LOCK_DIR, 'deepseek-serve.json')
29
+ const BASE_PORT = 17878
30
+
31
+ interface ServeLock {
32
+ pid: number
33
+ port: number
34
+ version: string
35
+ startedAt: string
36
+ /** Session ID → thread ID mappings. Threads can be resumed/forked across sessions. */
37
+ threads: Record<string, string>
38
+ }
39
+
40
+ // ── Lock file ───────────────────────────────────────────────────────────────
41
+
42
+ function readLock(): ServeLock | null {
43
+ try {
44
+ if (!existsSync(LOCK_FILE)) return null
45
+ return JSON.parse(readFileSync(LOCK_FILE, 'utf-8'))
46
+ } catch {
47
+ return null
48
+ }
49
+ }
50
+
51
+ function writeLock(lock: ServeLock): void {
52
+ mkdirSync(LOCK_DIR, { recursive: true })
53
+ writeFileSync(LOCK_FILE, JSON.stringify(lock, null, 2) + '\n')
54
+ }
55
+
56
+ function updateThreadMapping(sessionId: string, threadId: string): void {
57
+ const lock = readLock()
58
+ if (!lock) return
59
+ lock.threads[sessionId] = threadId
60
+ writeLock(lock)
61
+ }
62
+
63
+ function isProcessAlive(pid: number): boolean {
64
+ try {
65
+ process.kill(pid, 0)
66
+ return true
67
+ } catch {
68
+ return false
69
+ }
70
+ }
71
+
72
+ // ── Port finder ─────────────────────────────────────────────────────────────
73
+
74
+ async function findFreePort(start: number): Promise<number> {
75
+ const net = await import('node:net')
76
+ return new Promise((resolve, reject) => {
77
+ const server = net.createServer()
78
+ server.listen(start, '127.0.0.1', () => {
79
+ const port = (server.address() as any).port
80
+ server.close(() => resolve(port))
81
+ })
82
+ server.on('error', () => resolve(findFreePort(start + 1)))
83
+ })
84
+ }
85
+
86
+ // ── Version check ───────────────────────────────────────────────────────────
87
+
88
+ async function getVersion(): Promise<string | null> {
89
+ try {
90
+ const proc = spawn({ cmd: ['deepseek', '--version'], stdout: 'pipe', stderr: 'pipe' })
91
+ const out = await new Response(proc.stdout).text()
92
+ const match = out.match(/(\d+\.\d+\.\d+)/)
93
+ return match ? match[1] : null
94
+ } catch {
95
+ return null
96
+ }
97
+ }
98
+
99
+ // ── Serve lifecycle ─────────────────────────────────────────────────────────
100
+
101
+ let cachedPort: number | null = null
102
+
103
+ async function ensureServeRunning(): Promise<number> {
104
+ if (cachedPort !== null) {
105
+ // Quick health check
106
+ try {
107
+ const res = await fetch(`http://127.0.0.1:${cachedPort}/health`, { signal: AbortSignal.timeout(2000) })
108
+ if (res.ok) return cachedPort
109
+ } catch {}
110
+ cachedPort = null
111
+ }
112
+
113
+ // Check version
114
+ const version = await getVersion()
115
+ if (!version) throw new Error('DeepSeek CLI not found. Install: https://github.com/Hmbown/deepseek-tui')
116
+ if (!version.startsWith('0.8.')) {
117
+ console.warn(`⚠️ DeepSeek version ${version} — tested on 0.8.14. May behave differently.`)
118
+ }
119
+
120
+ // Check lock file
121
+ const lock = readLock()
122
+ if (lock && isProcessAlive(lock.pid)) {
123
+ // Verify health
124
+ try {
125
+ const res = await fetch(`http://127.0.0.1:${lock.port}/health`, { signal: AbortSignal.timeout(2000) })
126
+ if (res.ok) {
127
+ cachedPort = lock.port
128
+ return lock.port
129
+ }
130
+ } catch {}
131
+ }
132
+
133
+ // Start new serve instance
134
+ const port = await findFreePort(BASE_PORT)
135
+ console.log(`🔧 Starting DeepSeek serve on port ${port}...`)
136
+
137
+ const proc = spawn({
138
+ cmd: ['deepseek', 'serve', '--http', '--port', String(port)],
139
+ stdout: 'pipe',
140
+ stderr: 'pipe',
141
+ stdin: 'ignore',
142
+ })
143
+
144
+ // Wait for serve to be ready
145
+ for (let i = 0; i < 30; i++) {
146
+ try {
147
+ const res = await fetch(`http://127.0.0.1:${port}/health`, { signal: AbortSignal.timeout(1000) })
148
+ if (res.ok) {
149
+ writeLock({ pid: proc.pid, port, version, startedAt: new Date().toISOString(), threads: {} })
150
+ cachedPort = port
151
+ return port
152
+ }
153
+ } catch {}
154
+ await new Promise(r => setTimeout(r, 500))
155
+ }
156
+
157
+ throw new Error(`DeepSeek serve failed to start on port ${port}`)
158
+ }
159
+
160
+ // ── Thread API ──────────────────────────────────────────────────────────────
161
+
162
+ interface DeepSeekThread {
163
+ id: string
164
+ workspace: string
165
+ mode: string
166
+ }
167
+
168
+ interface DeepSeekEvent {
169
+ seq: number
170
+ event: string
171
+ payload: { delta?: string; kind?: string }
172
+ }
173
+
174
+ async function collectThreadOutput(threadId: string, port: number, timeoutMs: number): Promise<string> {
175
+ const deadline = Date.now() + timeoutMs
176
+ let output = ''
177
+
178
+ while (Date.now() < deadline) {
179
+ // Check if turn is complete
180
+ const threadRes = await fetch(
181
+ `http://127.0.0.1:${port}/v1/threads/${threadId}`,
182
+ { signal: AbortSignal.timeout(3000) }
183
+ ).catch(() => null)
184
+ if (!threadRes?.ok) { await new Promise(r => setTimeout(r, 2000)); continue }
185
+
186
+ const thread = await threadRes.json()
187
+ const turnId = thread.thread?.latest_turn_id ?? thread.latest_turn_id
188
+ if (!turnId) { await new Promise(r => setTimeout(r, 2000)); continue }
189
+
190
+ // Collect all events so far
191
+ const eventsRes = await fetch(
192
+ `http://127.0.0.1:${port}/v1/threads/${threadId}/events?since_seq=0`,
193
+ { signal: AbortSignal.timeout(5000) }
194
+ ).catch(() => null)
195
+ if (!eventsRes?.ok) { await new Promise(r => setTimeout(r, 2000)); continue }
196
+
197
+ const text = await eventsRes.text()
198
+ let completed = false
199
+ output = ''
200
+ for (const line of text.split('\n')) {
201
+ if (line.startsWith('data: ')) {
202
+ try {
203
+ const event: DeepSeekEvent = JSON.parse(line.slice(6))
204
+ if (event.payload?.delta) output += event.payload.delta
205
+ if (event.event === 'turn.completed') completed = true
206
+ } catch {}
207
+ }
208
+ }
209
+ if (completed && output) return output
210
+
211
+ await new Promise(r => setTimeout(r, 2000))
212
+ }
213
+
214
+ return output || '(timeout)'
215
+ }
216
+
217
+ // ── Session tracking ────────────────────────────────────────────────────────
218
+
219
+ let sessionCounter = 0
220
+
221
+ function nextSessionId(): string {
222
+ sessionCounter++
223
+ const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 15)
224
+ return `arena-${ts}-${String(sessionCounter).padStart(3, '0')}`
225
+ }
226
+
227
+ // ── Adapter ─────────────────────────────────────────────────────────────────
228
+
229
+ const deepseekServeAdapter: AgentAdapter = {
230
+ name: 'deepseek',
231
+
232
+ async spawn(opts): Promise<AgentRunResult> {
233
+ const startTime = Date.now()
234
+ const port = await ensureServeRunning()
235
+ const sessionId = nextSessionId()
236
+
237
+ // Create thread
238
+ const threadRes = await fetch(`http://127.0.0.1:${port}/v1/threads`, {
239
+ method: 'POST',
240
+ headers: { 'Content-Type': 'application/json' },
241
+ body: JSON.stringify({
242
+ workspace: opts.cwd,
243
+ mode: 'yolo',
244
+ auto_approve: true,
245
+ allow_shell: true,
246
+ }),
247
+ })
248
+ if (!threadRes.ok) {
249
+ throw new Error(`Failed to create thread: HTTP ${threadRes.status}`)
250
+ }
251
+ const thread: DeepSeekThread = await threadRes.json()
252
+ updateThreadMapping(sessionId, thread.id)
253
+
254
+ // Send turn
255
+ const turnRes = await fetch(`http://127.0.0.1:${port}/v1/threads/${thread.id}/turns`, {
256
+ method: 'POST',
257
+ headers: { 'Content-Type': 'application/json' },
258
+ body: JSON.stringify({ prompt: opts.brief }),
259
+ })
260
+ if (!turnRes.ok) {
261
+ throw new Error(`Failed to send turn: HTTP ${turnRes.status}`)
262
+ }
263
+
264
+ // Collect output
265
+ const stdout = await collectThreadOutput(thread.id, port, opts.timeoutMs)
266
+ const durationMs = Date.now() - startTime
267
+
268
+ return {
269
+ stdout,
270
+ stderr: '',
271
+ code: 0,
272
+ durationMs,
273
+ checkpoints: [],
274
+ }
275
+ },
276
+ }
277
+
278
+ registerAgent('deepseek', deepseekServeAdapter)
279
+
280
+ export { deepseekServeAdapter, ensureServeRunning }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ // ── @lythos/agent-adapter-deepseek-serve — DeepSeek serve-mode adapter ─
2
+ //
3
+ // Daemon lifecycle: start/stop/reuse deepseek serve --http process.
4
+ // Uses HTTP thread API for full agent execution with file ops, shell, subagents.
5
+ //
6
+ // Self-registers on import:
7
+ // import '@lythos/agent-adapter-deepseek-serve'
8
+ // import { useAgent } from '@lythos/agent-adapter'
9
+ // const agent = useAgent('deepseek')
10
+
11
+ export { deepseekServeAdapter, ensureServeRunning } from './deepseek-serve'