Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions docs/core-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# `@deepcode/core` API Reference

> **Status**: M1 — kernel MVP shipped. Surface area will grow per milestone.
> **Spec**: `DEVELOPMENT_PLAN.md` §3.1 (provider), §3.2 (tools), §3.5 (sessions).

## At a glance

```ts
import {
runAgent,
DeepSeekProvider,
ToolRegistry,
SessionManager,
BUILTIN_TOOLS,
} from '@deepcode/core';

const provider = new DeepSeekProvider({ apiKey: process.env.DEEPSEEK_API_KEY! });
const tools = new ToolRegistry(); // 6 P0 tools auto-registered
const sessions = new SessionManager(); // ~/.deepcode/sessions/ by default
const session = await sessions.create(process.cwd());

const result = await runAgent({
provider,
tools,
systemPrompt: 'You are DeepCode. Help with code.',
userMessage: 'List the TypeScript files in src/.',
model: 'deepseek-chat',
cwd: process.cwd(),
session: { manager: sessions, id: session.id },
enableSnapshots: true,
onEvent: (e) => {
if (e.type === 'text_delta') process.stdout.write(e.text);
},
});

console.log(`\n— ${result.turnsUsed} turns, ${result.usage.outputTokens} output tokens`);
```

## Exports

### Providers

| Symbol | Purpose |
| ------------------------------------ | ----------------------------------------------------------------------------------------- |
| `DeepSeekProvider` | OpenAI-compatible streaming provider for DeepSeek (`api.deepseek.com/v1`). |
| `DEEPSEEK_MODELS` | Per-model metadata: `ctx` (128k) + `maxOutput` (8192 hard limit). |
| `EFFORT_PARAMS` | 5-tier effort → `{ maxTokens, temperature }` mapping. See `docs/design/effort-levels.md`. |
| `Provider` | Interface — extend to add new LLM backends. |
| `ProviderResult` / `ProviderRunOpts` | Provider contract types. |

`DeepSeekProvider` options:

```ts
new DeepSeekProvider({
apiKey: 'sk-...', // OR
authToken: 'bearer-...', // Bearer alternative (§3.4 dual-header)
baseURL: 'https://api.deepseek.com/v1', // default
fetch: customFetch, // for tests
});
```

Streaming events flow through `ProviderStreamHandlers.onTextDelta` and `.onThinkingDelta`. The provider returns assembled `ContentBlock[]` (text / thinking / tool_use).

### Tools

Six P0 tools registered by default via `BUILTIN_TOOLS` and `ToolRegistry`:

| Tool | Input schema highlights |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `ReadTool` | `file_path` (abs or cwd-relative) + optional `offset` / `limit`. Returns line-numbered content. |
| `WriteTool` | `file_path` + `content`. Creates parent dirs. |
| `EditTool` | `file_path` + `old_string` + `new_string` (+ `replace_all`). Fails on missing or non-unique `old_string` (unless `replace_all`). |
| `BashTool` | `command` (+ `timeout`, `description`, `run_in_background` [M3.15.3 only]). Captures stdout/stderr/exitCode. |
| `GrepTool` | `pattern` + optional `path` / `glob` / `type` / `output_mode` / `-i` / `-n` / `head_limit`. Uses ripgrep. |
| `GlobTool` | `pattern` + optional `path` / `limit`. Built-in `fs.glob`. Sorts by mtime desc. |

Extend the registry:

```ts
const tools = new ToolRegistry();
tools.register(myCustomTool);
```

### Sessions

```ts
const sessions = new SessionManager({ root: '~/.deepcode/sessions' });

const meta = await sessions.create(cwd, { model: 'deepseek-chat', title: 'fix bug' });
await sessions.append(meta.id, message);
const loaded = await sessions.load(meta.id); // { meta, messages }
const list = await sessions.list(); // sorted by updatedAt desc

// Snapshots (pre/post Edit-Write, drives §3.15.9 rewind)
await sessions.snapshot({ sessionId: meta.id, cwd, filePath: 'a.ts', reason: 'pre-Edit', seq: 1 });
const snaps = await sessions.snapshots(meta.id);
await restoreSnapshot(snaps[0]!);
```

Storage layout:

```
<root>/<sessionId>.meta.json # meta JSON
<root>/<sessionId>.jsonl # one StoredMessage per line
<root>/<sessionId>/snapshots/ # blob files + manifest.jsonl
```

### Agent loop

```ts
const result = await runAgent({
provider, tools, systemPrompt, userMessage,
history: [], // resume from previous turns
model: 'deepseek-chat',
maxTokens: 4096,
temperature: 0.4,
maxTurns: 16, // safety cap
cwd: process.cwd(),
signal, // AbortSignal
session: { manager, id },
enableSnapshots: true,
onEvent: (e) => { ... },
});
// result.stopReason: 'end_turn' | 'max_turns' | 'aborted' | 'error'
// result.history: accumulated messages
// result.turnsUsed: provider round-trips
// result.usage: aggregate tokens
```

`AgentEvent` discriminants: `text_delta` / `thinking_delta` / `tool_use` / `tool_result` / `turn_complete` / `usage` / `error`.

## Type re-exports

All of `types.ts` is re-exported. Highlights:

- `ContentBlock = TextBlock | ToolUseBlock | ToolResultBlock | ThinkingBlock`
- `StoredMessage = { role, content, timestamp? }`
- `ToolDefinition` / `ToolContext` / `ToolResult` / `ToolHandler`
- `Mode` / `Effort` / `DeepSeekModel` / `HookEvent` / `HookHandlerType`

## What M1 does NOT include

Coming in later milestones (see `DEVELOPMENT_PLAN.md` §6):

| Feature | Milestone |
| -------------------------------------------------------------- | --------- |
| `--mode`, permissions matcher, trust dialog | M2 |
| 30+ slash commands wiring | M2 |
| settings.json three-layer config | M2 |
| Hooks (9 events × 5 handlers), MCP, memory, compaction | M3 |
| Sandbox subsystem (bwrap / sandbox-exec) | M3.5 |
| Skills, sub-agents, output styles, effort levels full plumbing | M4 |
| Plugin system + marketplace | M5 |
| Mac desktop client + auto-update | M6 |
| Right-side file panel + rewind UX | M7 |
| Vim mode, voice input, headless `-p` | M8 |

## Tests

`pnpm --filter @deepcode/core test` — 62 tests pass, 4 skipped (ripgrep-dependent if not installed). Coverage:

- 6 tool handlers (read/write/edit/bash/grep/glob)
- Sessions storage + snapshots roundtrip
- DeepSeekProvider mocked-fetch streaming + tool calls + reasoning_content + message-shape conversion
- Agent loop: end_turn / tool dispatch / unknown tool / maxTurns cap / abort signal / session persistence + snapshots / multi-turn history feeding

Run a single test file:

```bash
pnpm --filter @deepcode/core test -- src/agent.test.ts
```
72 changes: 72 additions & 0 deletions docs/milestones/M1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# M1 — Kernel MVP

> **Status**: ✅ complete
> **Branch**: `feat/m1-kernel-mvp`

## Scope (planned)

> DEVELOPMENT_PLAN.md §6:
> `@deepcode/core`: DeepSeekProvider + agent loop + 6 P0 tools + sessions(jsonl) + 文件快照(与 §3.15.9 rewind / §3.11 文件面板共底层)+ trust dialog 基础
> Tests: 单测:deepseek-chat 改文件;DeepSeek tool-calling 兼容性 matrix;reasoner 流式 fixture
> Docs: `docs/core-api.md`

## Delivered

| Module | Lines | Tests |
| ----------------------- | ---------- | ------------------------------------------------------------------------------------------------ |
| `providers/deepseek.ts` | 220 | 13 (streaming text / reasoning / tool calls / 5 message-shape conversions) |
| `providers/types.ts` | 38 | — |
| `tools/read.ts` | 80 | 6 |
| `tools/write.ts` | 56 | 5 |
| `tools/edit.ts` | 105 | 6 |
| `tools/bash.ts` | 102 | 5 (incl 1s timeout test) |
| `tools/grep.ts` | 113 | 5 (auto-skip if `rg` missing) |
| `tools/glob.ts` | 80 | 4 |
| `tools/registry.ts` | 42 | — |
| `sessions/storage.ts` | 113 | 6 |
| `sessions/snapshots.ts` | 96 | 5 |
| `sessions/manager.ts` | 75 | (via agent.test) |
| `agent.ts` | 195 | 7 (end_turn / tool dispatch / unknown / maxTurns / abort / session+snapshots / history feedback) |
| **Total** | **~1,400** | **62 passed · 4 skipped** |

## Verification

```bash
pnpm typecheck # green
pnpm build # green
pnpm test # 62 passed / 4 skipped / 0 failed
```

The 4 skipped tests are `tools/grep.test.ts` cases that require `ripgrep` (rg) on PATH. CI (GitHub Actions Ubuntu) has it; local dev machines may not.

## NOT delivered (M1 spec said "trust dialog basics")

Trust dialog deferred to M2 — it needs the CLI/onboarding surface and `settings.json` integration to be useful, both of which are M2 scope. The kernel exposes session/snapshot primitives that the M2 trust dialog will use.

## Effort levels

`EFFORT_PARAMS` is exported with the design values from `docs/design/effort-levels.md` §3.2. **The numbers are not yet measured against real DeepSeek API** — that benchmark (`scripts/effort-bench.ts`) is deferred until a real `DEEPSEEK_API_KEY` is configured in CI secrets. The values stay within the documented 8,192 max_tokens hard limit (asserted in tests).

## Key design decisions made

1. **DeepSeek-internal `ContentBlock` types** (not Anthropic-shape) — `text / tool_use / tool_result / thinking`. Providers convert at the boundary. Avoids a hard `@anthropic-ai/sdk` dep.

2. **`anthropicShapeToOpenAI` boundary converter** — DeepCode-internal history → OpenAI chat-completions shape. Handles assistant tool_calls + role:"tool" results + thinking-block stripping.

3. **History is snapshotted into each provider call** (`messages: [...history]`) — prevents subsequent turns' mutations from changing what an earlier call "saw". Also makes provider-call replay deterministic.

4. **Snapshots live alongside sessions** — `<sessionsRoot>/<sessionId>/snapshots/{NNNNN-ts-hash.blob, manifest.jsonl}`. Same storage that §3.15.9 rewind will use; not a separate subsystem.

5. **Bash `run_in_background` is a deliberate stub** — returns an error pointing to M3.15.3. Defers the entire background-task infrastructure to where its design doc (TaskCreate / Monitor / TaskOutput) lives.

6. **No `nock`/`msw` dep for provider tests** — OpenAI SDK accepts a `fetch` injection; tests pass a `mockFetch(chunks)` that returns SSE `data:` lines. Zero extra deps, full streaming coverage.

## Pivots & learnings

- **Initial `messages` type cast** — OpenAI SDK's generated types are stricter than the DeepSeek wire format actually requires. Cast at the API boundary (one place) rather than fight strict types throughout.
- **`apiKey ?? authToken` was wrong** — empty string `''` isn't nullish so `??` doesn't fall through. Fixed to `||`. Test caught this immediately.
- **History mutation bug** — `messages: history` passed by reference, leading to a subtle test failure where the second provider call's recorded messages were "later" than the moment of the call. Fixed with `[...history]` snapshot.

## Next: M2

M2 adds the CLI: onboarding (with API key entry + Keychain + `apiKeyHelper`), REPL, 30+ slash commands, `settings.json` three-layer loader, `permissions` matcher (both glob syntaxes), trust dialog. The kernel exposes everything M2 needs.
Loading
Loading