codeep 1.2.18 → 1.2.20

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/README.md CHANGED
@@ -764,3 +764,37 @@ Contributions are welcome! Please open an issue or submit a pull request on [Git
764
764
  ## Support
765
765
 
766
766
  - **Issues**: [GitHub Issues](https://github.com/VladoIvankovic/Codeep/issues)
767
+
768
+ ## Zed Editor Integration (ACP)
769
+
770
+ Codeep supports the [Agent Client Protocol (ACP)](https://agentclientprotocol.com), letting you use it as an AI coding agent directly inside [Zed](https://zed.dev).
771
+
772
+ ### Setup
773
+
774
+ 1. Install Codeep globally:
775
+
776
+ ```bash
777
+ npm install -g codeep
778
+ ```
779
+
780
+ 2. Add to your Zed `settings.json`:
781
+
782
+ ```json
783
+ {
784
+ "agent_servers": {
785
+ "Codeep": {
786
+ "type": "custom",
787
+ "command": "codeep",
788
+ "args": ["acp"]
789
+ }
790
+ }
791
+ }
792
+ ```
793
+
794
+ 3. Make sure your API key is set in the environment Zed uses:
795
+
796
+ ```bash
797
+ export DEEPSEEK_API_KEY=your_key # or whichever provider
798
+ ```
799
+
800
+ 4. Open Zed's AI panel and select **Codeep** as the agent.
@@ -0,0 +1,54 @@
1
+ export interface JsonRpcRequest {
2
+ jsonrpc: '2.0';
3
+ id: number | string;
4
+ method: string;
5
+ params?: unknown;
6
+ }
7
+ export interface JsonRpcResponse {
8
+ jsonrpc: '2.0';
9
+ id: number | string;
10
+ result?: unknown;
11
+ error?: {
12
+ code: number;
13
+ message: string;
14
+ };
15
+ }
16
+ export interface JsonRpcNotification {
17
+ jsonrpc: '2.0';
18
+ method: string;
19
+ params?: unknown;
20
+ }
21
+ export interface InitializeParams {
22
+ capabilities?: Record<string, unknown>;
23
+ workspaceFolders?: {
24
+ uri: string;
25
+ name: string;
26
+ }[];
27
+ }
28
+ export interface InitializeResult {
29
+ capabilities: {
30
+ streaming?: boolean;
31
+ fileEditing?: boolean;
32
+ };
33
+ serverInfo: {
34
+ name: string;
35
+ version: string;
36
+ };
37
+ }
38
+ export interface AgentRunParams {
39
+ prompt: string;
40
+ workspaceRoot?: string;
41
+ conversationId?: string;
42
+ }
43
+ export interface AgentStreamParams {
44
+ conversationId: string;
45
+ text: string;
46
+ done?: boolean;
47
+ }
48
+ export interface ApplyEditParams {
49
+ changes: {
50
+ uri: string;
51
+ newText: string;
52
+ }[];
53
+ }
54
+ export type AcpMessage = JsonRpcRequest | JsonRpcResponse | JsonRpcNotification;
@@ -0,0 +1,3 @@
1
+ // acp/protocol.ts
2
+ // ACP JSON-RPC message types for Zed Agent Client Protocol
3
+ export {};
@@ -0,0 +1 @@
1
+ export declare function startAcpServer(): Promise<void>;
@@ -0,0 +1,81 @@
1
+ // src/acp/server.ts
2
+ // Codeep ACP adapter — started via `codeep acp` CLI subcommand
3
+ import { StdioTransport } from './transport.js';
4
+ import { runAgentSession } from './session.js';
5
+ export function startAcpServer() {
6
+ const transport = new StdioTransport();
7
+ const activeSessions = new Map();
8
+ transport.start((msg) => {
9
+ switch (msg.method) {
10
+ case 'initialize':
11
+ handleInitialize(msg);
12
+ break;
13
+ case 'initialized':
14
+ // no-op acknowledgment
15
+ break;
16
+ case 'agent/run':
17
+ handleAgentRun(msg);
18
+ break;
19
+ case 'agent/cancel':
20
+ handleAgentCancel(msg);
21
+ break;
22
+ default:
23
+ transport.error(msg.id, -32601, `Method not found: ${msg.method}`);
24
+ }
25
+ });
26
+ function handleInitialize(msg) {
27
+ const _params = msg.params;
28
+ const result = {
29
+ capabilities: {
30
+ streaming: true,
31
+ fileEditing: true,
32
+ },
33
+ serverInfo: {
34
+ name: 'codeep',
35
+ version: '1.0.0',
36
+ },
37
+ };
38
+ transport.respond(msg.id, result);
39
+ }
40
+ function handleAgentRun(msg) {
41
+ const params = msg.params;
42
+ const conversationId = params.conversationId ?? String(msg.id);
43
+ const abortController = new AbortController();
44
+ activeSessions.set(conversationId, abortController);
45
+ runAgentSession({
46
+ prompt: params.prompt,
47
+ workspaceRoot: params.workspaceRoot ?? process.cwd(),
48
+ conversationId,
49
+ abortSignal: abortController.signal,
50
+ onChunk: (text) => {
51
+ transport.notify('agent/stream', { conversationId, text });
52
+ },
53
+ onFileEdit: (uri, newText) => {
54
+ transport.notify('workspace/applyEdit', {
55
+ changes: [{ uri, newText }],
56
+ });
57
+ },
58
+ }).then(() => {
59
+ transport.respond(msg.id, { done: true });
60
+ }).catch((err) => {
61
+ if (err.name === 'AbortError') {
62
+ transport.respond(msg.id, { cancelled: true });
63
+ }
64
+ else {
65
+ transport.error(msg.id, -32000, err.message);
66
+ }
67
+ }).finally(() => {
68
+ activeSessions.delete(conversationId);
69
+ });
70
+ }
71
+ function handleAgentCancel(msg) {
72
+ const { conversationId } = msg.params;
73
+ activeSessions.get(conversationId)?.abort();
74
+ activeSessions.delete(conversationId);
75
+ transport.respond(msg.id, { cancelled: true });
76
+ }
77
+ // Keep process alive until stdin closes (Zed terminates us)
78
+ return new Promise((resolve) => {
79
+ process.stdin.on('end', resolve);
80
+ });
81
+ }
@@ -0,0 +1,15 @@
1
+ export interface AgentSessionOptions {
2
+ prompt: string;
3
+ workspaceRoot: string;
4
+ conversationId: string;
5
+ abortSignal: AbortSignal;
6
+ onChunk: (text: string) => void;
7
+ onFileEdit: (uri: string, newText: string) => void;
8
+ }
9
+ /**
10
+ * Run a single agent session driven by ACP parameters.
11
+ *
12
+ * onFileEdit is reserved for future use (v1 emits everything via onChunk).
13
+ */
14
+ export declare function runAgentSession(opts: AgentSessionOptions): Promise<void>;
15
+ export declare function pathToUri(absolutePath: string): string;
@@ -0,0 +1,61 @@
1
+ // acp/session.ts
2
+ // Bridges ACP parameters to the Codeep agent loop.
3
+ import { pathToFileURL } from 'url';
4
+ import { runAgent } from '../utils/agent.js';
5
+ import { getProjectContext } from '../utils/project.js';
6
+ /**
7
+ * Build a ProjectContext from a workspace root directory.
8
+ * Falls back to a minimal synthetic context if scanning fails.
9
+ */
10
+ function buildProjectContext(workspaceRoot) {
11
+ const ctx = getProjectContext(workspaceRoot);
12
+ if (ctx) {
13
+ return ctx;
14
+ }
15
+ // Minimal fallback so runAgent never receives null
16
+ return {
17
+ root: workspaceRoot,
18
+ name: workspaceRoot.split('/').pop() ?? 'workspace',
19
+ type: 'Unknown',
20
+ structure: '',
21
+ keyFiles: [],
22
+ fileCount: 0,
23
+ summary: `Workspace at ${workspaceRoot}`,
24
+ };
25
+ }
26
+ /**
27
+ * Run a single agent session driven by ACP parameters.
28
+ *
29
+ * onFileEdit is reserved for future use (v1 emits everything via onChunk).
30
+ */
31
+ export async function runAgentSession(opts) {
32
+ const projectContext = buildProjectContext(opts.workspaceRoot);
33
+ const result = await runAgent(opts.prompt, projectContext, {
34
+ abortSignal: opts.abortSignal,
35
+ // Stream iteration status and thinking text back to the caller via onChunk
36
+ onIteration: (_iteration, message) => {
37
+ opts.onChunk(message + '\n');
38
+ },
39
+ onThinking: (text) => {
40
+ opts.onChunk(text);
41
+ },
42
+ });
43
+ // Emit the final response text if present
44
+ if (result.finalResponse) {
45
+ opts.onChunk(result.finalResponse);
46
+ }
47
+ // Surface errors as thrown exceptions so index.ts can handle them correctly
48
+ if (!result.success && !result.aborted) {
49
+ throw new Error(result.error ?? 'Agent run failed without a specific error message');
50
+ }
51
+ if (result.aborted) {
52
+ const abortError = new Error('Agent session was cancelled');
53
+ abortError.name = 'AbortError';
54
+ throw abortError;
55
+ }
56
+ }
57
+ // Utility: convert an absolute file-system path to a file:// URI string.
58
+ // Exported for use by callers that need to construct applyEdit URIs.
59
+ export function pathToUri(absolutePath) {
60
+ return pathToFileURL(absolutePath).href;
61
+ }
@@ -0,0 +1,13 @@
1
+ import { JsonRpcRequest, JsonRpcResponse, JsonRpcNotification } from './protocol.js';
2
+ type MessageHandler = (msg: JsonRpcRequest) => void;
3
+ export declare class StdioTransport {
4
+ private buffer;
5
+ private handler;
6
+ start(handler: MessageHandler): void;
7
+ private onData;
8
+ send(msg: JsonRpcResponse | JsonRpcNotification): void;
9
+ respond(id: number | string, result: unknown): void;
10
+ error(id: number | string, code: number, message: string): void;
11
+ notify(method: string, params: unknown): void;
12
+ }
13
+ export {};
@@ -0,0 +1,41 @@
1
+ // acp/transport.ts
2
+ // Newline-delimited JSON-RPC over stdio
3
+ export class StdioTransport {
4
+ buffer = '';
5
+ handler = null;
6
+ start(handler) {
7
+ this.handler = handler;
8
+ process.stdin.setEncoding('utf8');
9
+ process.stdin.on('data', (chunk) => this.onData(chunk));
10
+ process.stdin.on('end', () => process.exit(0));
11
+ }
12
+ onData(chunk) {
13
+ this.buffer += chunk;
14
+ const lines = this.buffer.split('\n');
15
+ this.buffer = lines.pop() ?? '';
16
+ for (const line of lines) {
17
+ const trimmed = line.trim();
18
+ if (!trimmed)
19
+ continue;
20
+ try {
21
+ const msg = JSON.parse(trimmed);
22
+ this.handler?.(msg);
23
+ }
24
+ catch {
25
+ // ignore malformed messages
26
+ }
27
+ }
28
+ }
29
+ send(msg) {
30
+ process.stdout.write(JSON.stringify(msg) + '\n');
31
+ }
32
+ respond(id, result) {
33
+ this.send({ jsonrpc: '2.0', id, result });
34
+ }
35
+ error(id, code, message) {
36
+ this.send({ jsonrpc: '2.0', id, error: { code, message } });
37
+ }
38
+ notify(method, params) {
39
+ this.send({ jsonrpc: '2.0', method, params });
40
+ }
41
+ }
@@ -23,6 +23,7 @@ export interface ProviderConfig {
23
23
  }[];
24
24
  defaultModel: string;
25
25
  defaultProtocol: 'openai' | 'anthropic';
26
+ maxOutputTokens?: number;
26
27
  envKey?: string;
27
28
  subscribeUrl?: string;
28
29
  mcpEndpoints?: {
@@ -49,3 +50,8 @@ export declare function getProviderBaseUrl(providerId: string, protocol: 'openai
49
50
  export declare function getProviderAuthHeader(providerId: string, protocol: 'openai' | 'anthropic'): 'Bearer' | 'x-api-key';
50
51
  export declare function getProviderMcpEndpoints(providerId: string): ProviderConfig['mcpEndpoints'] | null;
51
52
  export declare function supportsNativeTools(providerId: string, protocol: 'openai' | 'anthropic'): boolean;
53
+ /**
54
+ * Returns the effective max output tokens for a provider, capped by the provider's limit.
55
+ * Falls back to the requested value if no provider limit is set.
56
+ */
57
+ export declare function getEffectiveMaxTokens(providerId: string, requested: number): number;
@@ -124,6 +124,7 @@ export const PROVIDERS = {
124
124
  ],
125
125
  defaultModel: 'deepseek-chat',
126
126
  defaultProtocol: 'openai',
127
+ maxOutputTokens: 8192, // DeepSeek API limit
127
128
  envKey: 'DEEPSEEK_API_KEY',
128
129
  subscribeUrl: 'https://platform.deepseek.com/sign_up',
129
130
  },
@@ -185,3 +186,13 @@ export function supportsNativeTools(providerId, protocol) {
185
186
  return false;
186
187
  return provider.protocols[protocol]?.supportsNativeTools ?? true; // Default to true
187
188
  }
189
+ /**
190
+ * Returns the effective max output tokens for a provider, capped by the provider's limit.
191
+ * Falls back to the requested value if no provider limit is set.
192
+ */
193
+ export function getEffectiveMaxTokens(providerId, requested) {
194
+ const provider = PROVIDERS[providerId];
195
+ if (!provider?.maxOutputTokens)
196
+ return requested;
197
+ return Math.min(requested, provider.maxOutputTokens);
198
+ }
@@ -321,6 +321,7 @@ Codeep - AI-powered coding assistant TUI
321
321
 
322
322
  Usage:
323
323
  codeep Start interactive chat
324
+ codeep acp Start ACP server (for Zed editor integration)
324
325
  codeep --version Show version
325
326
  codeep --help Show this help
326
327
 
@@ -332,6 +333,12 @@ Commands (in chat):
332
333
  `);
333
334
  process.exit(0);
334
335
  }
336
+ // ACP server mode — started by Zed via Agent Client Protocol
337
+ if (args[0] === 'acp') {
338
+ const { startAcpServer } = await import('../acp/server.js');
339
+ await startAcpServer();
340
+ return;
341
+ }
335
342
  await loadAllApiKeys();
336
343
  let apiKey = await loadApiKey();
337
344
  if (!apiKey) {
@@ -4,8 +4,10 @@
4
4
  * Private chat/stream logic lives in agentChat.ts and agentStream.ts.
5
5
  */
6
6
  import { ProjectContext } from './project';
7
- import { loadProjectRules, formatChatHistoryForAgent, AgentChatResponse } from './agentChat';
8
- export { loadProjectRules, formatChatHistoryForAgent, AgentChatResponse };
7
+ import { loadProjectRules, formatChatHistoryForAgent } from './agentChat';
8
+ import type { AgentChatResponse } from './agentChat';
9
+ export { loadProjectRules, formatChatHistoryForAgent };
10
+ export type { AgentChatResponse };
9
11
  import { ToolCall, ToolResult, ActionLog } from './tools';
10
12
  import { undoLastAction, undoAllActions, getCurrentSession, getRecentSessions, formatSession, ActionSession } from './history';
11
13
  import { VerifyResult } from './verify';
@@ -13,8 +13,8 @@
13
13
  */
14
14
  import { ProjectContext } from './project';
15
15
  import { Message } from '../config/index';
16
- import { AgentChatResponse } from './agentStream';
17
- export { AgentChatResponse };
16
+ import type { AgentChatResponse } from './agentStream';
17
+ export type { AgentChatResponse };
18
18
  /**
19
19
  * Custom error class for timeout
20
20
  */
@@ -14,7 +14,7 @@
14
14
  import { existsSync, readFileSync } from 'fs';
15
15
  import { join } from 'path';
16
16
  import { config, getApiKey } from '../config/index.js';
17
- import { getProviderBaseUrl, getProviderAuthHeader, supportsNativeTools } from '../config/providers.js';
17
+ import { getProviderBaseUrl, getProviderAuthHeader, supportsNativeTools, getEffectiveMaxTokens } from '../config/providers.js';
18
18
  import { recordTokenUsage, extractOpenAIUsage, extractAnthropicUsage } from './tokenTracker.js';
19
19
  import { parseOpenAIToolCalls, parseAnthropicToolCalls, parseToolCalls } from './toolParsing.js';
20
20
  import { formatToolDefinitions, getOpenAITools, getAnthropicTools } from './tools.js';
@@ -174,7 +174,7 @@ export async function agentChat(messages, systemPrompt, onChunk, abortSignal, dy
174
174
  body = {
175
175
  model, messages: [{ role: 'system', content: systemPrompt }, ...messages],
176
176
  tools: getOpenAITools(), tool_choice: 'auto', stream: useStreaming,
177
- temperature: config.get('temperature'), max_tokens: Math.max(config.get('maxTokens'), 16384),
177
+ temperature: config.get('temperature'), max_tokens: getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384)),
178
178
  };
179
179
  }
180
180
  else {
@@ -182,7 +182,7 @@ export async function agentChat(messages, systemPrompt, onChunk, abortSignal, dy
182
182
  body = {
183
183
  model, system: systemPrompt, messages,
184
184
  tools: getAnthropicTools(), stream: useStreaming,
185
- temperature: config.get('temperature'), max_tokens: Math.max(config.get('maxTokens'), 16384),
185
+ temperature: config.get('temperature'), max_tokens: getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384)),
186
186
  };
187
187
  }
188
188
  const response = await fetch(endpoint, {
@@ -290,7 +290,7 @@ export async function agentChatFallback(messages, systemPrompt, onChunk, abortSi
290
290
  body = {
291
291
  model, messages: [{ role: 'system', content: fallbackPrompt }, ...messages],
292
292
  stream: Boolean(onChunk), temperature: config.get('temperature'),
293
- max_tokens: Math.max(config.get('maxTokens'), 16384),
293
+ max_tokens: getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384)),
294
294
  };
295
295
  }
296
296
  else {
@@ -303,7 +303,7 @@ export async function agentChatFallback(messages, systemPrompt, onChunk, abortSi
303
303
  ...messages,
304
304
  ],
305
305
  stream: Boolean(onChunk), temperature: config.get('temperature'),
306
- max_tokens: Math.max(config.get('maxTokens'), 16384),
306
+ max_tokens: getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384)),
307
307
  };
308
308
  }
309
309
  const response = await fetch(endpoint, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "1.2.18",
3
+ "version": "1.2.20",
4
4
  "description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",