@dugleelabs/copair 1.2.0 → 1.4.0
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 +49 -0
- package/dist/api.d.ts +738 -0
- package/dist/api.js +7799 -0
- package/dist/api.js.map +1 -0
- package/dist/index.js +896 -271
- package/dist/index.js.map +1 -1
- package/package.json +15 -4
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
import { z, ZodTypeAny } from 'zod';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
|
|
4
|
+
interface Message {
|
|
5
|
+
role: 'user' | 'assistant' | 'system';
|
|
6
|
+
content: ContentBlock[];
|
|
7
|
+
}
|
|
8
|
+
type ContentBlock = {
|
|
9
|
+
type: 'text';
|
|
10
|
+
text: string;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'tool_use';
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
input: Record<string, unknown>;
|
|
16
|
+
metadata?: Record<string, unknown>;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'tool_result';
|
|
19
|
+
toolUseId: string;
|
|
20
|
+
content: string;
|
|
21
|
+
isError?: boolean;
|
|
22
|
+
};
|
|
23
|
+
interface StreamChunk {
|
|
24
|
+
type: 'text' | 'tool_call' | 'tool_call_delta' | 'usage' | 'error' | 'done';
|
|
25
|
+
text?: string;
|
|
26
|
+
toolCall?: {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
arguments: string;
|
|
30
|
+
metadata?: Record<string, unknown>;
|
|
31
|
+
};
|
|
32
|
+
usage?: {
|
|
33
|
+
inputTokens: number;
|
|
34
|
+
outputTokens: number;
|
|
35
|
+
};
|
|
36
|
+
error?: string;
|
|
37
|
+
}
|
|
38
|
+
interface ProviderOptions {
|
|
39
|
+
model: string;
|
|
40
|
+
maxTokens?: number;
|
|
41
|
+
temperature?: number;
|
|
42
|
+
systemPrompt?: string;
|
|
43
|
+
stream: boolean;
|
|
44
|
+
}
|
|
45
|
+
interface Provider {
|
|
46
|
+
readonly name: string;
|
|
47
|
+
readonly supportsToolCalling: boolean;
|
|
48
|
+
readonly supportsStreaming: boolean;
|
|
49
|
+
readonly maxContextWindow: number;
|
|
50
|
+
/** When true, the provider can fall back to a built-in web search tool when the agent's configured web search fails. */
|
|
51
|
+
readonly supportsNativeSearch?: boolean;
|
|
52
|
+
chat(messages: Message[], tools: ToolDefinition$1[], options: ProviderOptions): AsyncIterableIterator<StreamChunk>;
|
|
53
|
+
countTokens?(messages: Message[]): Promise<number>;
|
|
54
|
+
}
|
|
55
|
+
interface ToolDefinition$1 {
|
|
56
|
+
name: string;
|
|
57
|
+
description: string;
|
|
58
|
+
inputSchema: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
declare const ProviderConfigSchema: z.ZodObject<{
|
|
62
|
+
api_key: z.ZodOptional<z.ZodString>;
|
|
63
|
+
base_url: z.ZodOptional<z.ZodString>;
|
|
64
|
+
type: z.ZodOptional<z.ZodEnum<{
|
|
65
|
+
anthropic: "anthropic";
|
|
66
|
+
openai: "openai";
|
|
67
|
+
google: "google";
|
|
68
|
+
"openai-compatible": "openai-compatible";
|
|
69
|
+
}>>;
|
|
70
|
+
models: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
71
|
+
id: z.ZodString;
|
|
72
|
+
max_tokens: z.ZodOptional<z.ZodNumber>;
|
|
73
|
+
context_window: z.ZodOptional<z.ZodNumber>;
|
|
74
|
+
supports_tool_calling: z.ZodOptional<z.ZodBoolean>;
|
|
75
|
+
supports_streaming: z.ZodOptional<z.ZodBoolean>;
|
|
76
|
+
tool_call_format: z.ZodOptional<z.ZodEnum<{
|
|
77
|
+
dsml: "dsml";
|
|
78
|
+
"qwen-xml": "qwen-xml";
|
|
79
|
+
"fenced-block": "fenced-block";
|
|
80
|
+
}>>;
|
|
81
|
+
}, z.core.$strip>>;
|
|
82
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
83
|
+
}, z.core.$strip>;
|
|
84
|
+
declare const CopairConfigSchema: z.ZodObject<{
|
|
85
|
+
version: z.ZodNumber;
|
|
86
|
+
default_model: z.ZodOptional<z.ZodString>;
|
|
87
|
+
providers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
88
|
+
api_key: z.ZodOptional<z.ZodString>;
|
|
89
|
+
base_url: z.ZodOptional<z.ZodString>;
|
|
90
|
+
type: z.ZodOptional<z.ZodEnum<{
|
|
91
|
+
anthropic: "anthropic";
|
|
92
|
+
openai: "openai";
|
|
93
|
+
google: "google";
|
|
94
|
+
"openai-compatible": "openai-compatible";
|
|
95
|
+
}>>;
|
|
96
|
+
models: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
97
|
+
id: z.ZodString;
|
|
98
|
+
max_tokens: z.ZodOptional<z.ZodNumber>;
|
|
99
|
+
context_window: z.ZodOptional<z.ZodNumber>;
|
|
100
|
+
supports_tool_calling: z.ZodOptional<z.ZodBoolean>;
|
|
101
|
+
supports_streaming: z.ZodOptional<z.ZodBoolean>;
|
|
102
|
+
tool_call_format: z.ZodOptional<z.ZodEnum<{
|
|
103
|
+
dsml: "dsml";
|
|
104
|
+
"qwen-xml": "qwen-xml";
|
|
105
|
+
"fenced-block": "fenced-block";
|
|
106
|
+
}>>;
|
|
107
|
+
}, z.core.$strip>>;
|
|
108
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
109
|
+
}, z.core.$strip>>>;
|
|
110
|
+
permissions: z.ZodDefault<z.ZodObject<{
|
|
111
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
112
|
+
ask: "ask";
|
|
113
|
+
"auto-approve": "auto-approve";
|
|
114
|
+
deny: "deny";
|
|
115
|
+
}>>;
|
|
116
|
+
allow_commands: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
117
|
+
allow_paths: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
118
|
+
deny_paths: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
119
|
+
}, z.core.$strip>>;
|
|
120
|
+
feature_flags: z.ZodDefault<z.ZodObject<{
|
|
121
|
+
model_routing: z.ZodDefault<z.ZodBoolean>;
|
|
122
|
+
}, z.core.$strip>>;
|
|
123
|
+
mcp_servers: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
124
|
+
name: z.ZodString;
|
|
125
|
+
command: z.ZodString;
|
|
126
|
+
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
127
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
128
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
129
|
+
inherit_env: z.ZodOptional<z.ZodBoolean>;
|
|
130
|
+
}, z.core.$strip>>>;
|
|
131
|
+
plugins: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
132
|
+
web_search: z.ZodOptional<z.ZodObject<{
|
|
133
|
+
provider: z.ZodEnum<{
|
|
134
|
+
tavily: "tavily";
|
|
135
|
+
serper: "serper";
|
|
136
|
+
searxng: "searxng";
|
|
137
|
+
}>;
|
|
138
|
+
api_key: z.ZodOptional<z.ZodString>;
|
|
139
|
+
base_url: z.ZodOptional<z.ZodString>;
|
|
140
|
+
max_results: z.ZodDefault<z.ZodNumber>;
|
|
141
|
+
}, z.core.$strip>>;
|
|
142
|
+
identity: z.ZodDefault<z.ZodObject<{
|
|
143
|
+
name: z.ZodDefault<z.ZodString>;
|
|
144
|
+
email: z.ZodDefault<z.ZodString>;
|
|
145
|
+
}, z.core.$strip>>;
|
|
146
|
+
context: z.ZodDefault<z.ZodObject<{
|
|
147
|
+
summarization_model: z.ZodOptional<z.ZodString>;
|
|
148
|
+
max_sessions: z.ZodDefault<z.ZodNumber>;
|
|
149
|
+
knowledge_max_size: z.ZodDefault<z.ZodNumber>;
|
|
150
|
+
}, z.core.$strip>>;
|
|
151
|
+
knowledge: z.ZodDefault<z.ZodObject<{
|
|
152
|
+
warn_size_kb: z.ZodDefault<z.ZodNumber>;
|
|
153
|
+
max_size_kb: z.ZodDefault<z.ZodNumber>;
|
|
154
|
+
}, z.core.$strip>>;
|
|
155
|
+
ui: z.ZodDefault<z.ZodObject<{
|
|
156
|
+
bordered_input: z.ZodDefault<z.ZodBoolean>;
|
|
157
|
+
status_bar: z.ZodDefault<z.ZodBoolean>;
|
|
158
|
+
syntax_highlight: z.ZodDefault<z.ZodBoolean>;
|
|
159
|
+
output_collapsing: z.ZodDefault<z.ZodBoolean>;
|
|
160
|
+
vi_mode: z.ZodDefault<z.ZodBoolean>;
|
|
161
|
+
suggestions: z.ZodDefault<z.ZodBoolean>;
|
|
162
|
+
tab_completion: z.ZodDefault<z.ZodBoolean>;
|
|
163
|
+
}, z.core.$strip>>;
|
|
164
|
+
security: z.ZodOptional<z.ZodObject<{
|
|
165
|
+
path_validation: z.ZodDefault<z.ZodEnum<{
|
|
166
|
+
strict: "strict";
|
|
167
|
+
warn: "warn";
|
|
168
|
+
}>>;
|
|
169
|
+
redact_high_entropy: z.ZodDefault<z.ZodBoolean>;
|
|
170
|
+
}, z.core.$strip>>;
|
|
171
|
+
network: z.ZodOptional<z.ZodObject<{
|
|
172
|
+
web_search_timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
173
|
+
provider_timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
174
|
+
}, z.core.$strip>>;
|
|
175
|
+
}, z.core.$strip>;
|
|
176
|
+
type CopairConfig = z.infer<typeof CopairConfigSchema>;
|
|
177
|
+
type ProviderConfig = z.infer<typeof ProviderConfigSchema>;
|
|
178
|
+
|
|
179
|
+
type ProviderFactory = (config: ProviderConfig, model: string) => Provider;
|
|
180
|
+
declare class ProviderRegistry {
|
|
181
|
+
private factories;
|
|
182
|
+
private instances;
|
|
183
|
+
register(name: string, factory: ProviderFactory): void;
|
|
184
|
+
resolve(providerName: string, config: ProviderConfig, model: string): Provider;
|
|
185
|
+
has(name: string): boolean;
|
|
186
|
+
availableProviders(): string[];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface ToolDefinition {
|
|
190
|
+
name: string;
|
|
191
|
+
description: string;
|
|
192
|
+
inputSchema: Record<string, unknown>;
|
|
193
|
+
}
|
|
194
|
+
interface ToolResult {
|
|
195
|
+
content: string;
|
|
196
|
+
isError?: boolean;
|
|
197
|
+
}
|
|
198
|
+
interface Tool {
|
|
199
|
+
definition: ToolDefinition;
|
|
200
|
+
/**
|
|
201
|
+
* Zod schema for runtime validation of tool input before execution (FR-02).
|
|
202
|
+
* Required for all built-in tools. MCP tools omit this field and receive
|
|
203
|
+
* no schema validation (passthrough).
|
|
204
|
+
*/
|
|
205
|
+
inputSchema?: ZodTypeAny;
|
|
206
|
+
requiresPermission: boolean;
|
|
207
|
+
execute(input: Record<string, unknown>): Promise<ToolResult>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
declare class ToolRegistry {
|
|
211
|
+
private builtinTools;
|
|
212
|
+
private mcpTools;
|
|
213
|
+
register(tool: Tool): void;
|
|
214
|
+
registerMcpTools(serverName: string, tools: Tool[]): void;
|
|
215
|
+
get(name: string): Tool | undefined;
|
|
216
|
+
getAllDefinitions(): ToolDefinition[];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
interface AllowRules {
|
|
220
|
+
/** bash entries: exact match, or prefix if the pattern ends with " *" */
|
|
221
|
+
bash: string[];
|
|
222
|
+
/**
|
|
223
|
+
* git entries: matched against the args string.
|
|
224
|
+
* Entry is the subcommand (e.g. "diff") — automatically covers all
|
|
225
|
+
* flags for that subcommand (e.g. "diff --cached", "diff HEAD~1").
|
|
226
|
+
*/
|
|
227
|
+
git: string[];
|
|
228
|
+
/**
|
|
229
|
+
* write / edit entries: glob patterns matched against the file path.
|
|
230
|
+
* Supports * (within a segment) and ** (across segments).
|
|
231
|
+
*/
|
|
232
|
+
write: string[];
|
|
233
|
+
edit: string[];
|
|
234
|
+
}
|
|
235
|
+
declare class AllowList {
|
|
236
|
+
private rules;
|
|
237
|
+
constructor(rules?: Partial<AllowRules>);
|
|
238
|
+
/**
|
|
239
|
+
* Returns true when the operation is explicitly listed and should bypass
|
|
240
|
+
* the approval prompt. Called by ApprovalGate before prompting.
|
|
241
|
+
*/
|
|
242
|
+
matches(toolName: string, input: Record<string, unknown>): boolean;
|
|
243
|
+
private matchBash;
|
|
244
|
+
private matchGit;
|
|
245
|
+
private matchPath;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
interface ToolInfo {
|
|
249
|
+
name: string;
|
|
250
|
+
label: string;
|
|
251
|
+
input: Record<string, unknown>;
|
|
252
|
+
}
|
|
253
|
+
interface ToolCompleteInfo {
|
|
254
|
+
name: string;
|
|
255
|
+
label: string;
|
|
256
|
+
durationMs: number;
|
|
257
|
+
result?: string;
|
|
258
|
+
}
|
|
259
|
+
interface DiffHunk {
|
|
260
|
+
oldStart: number;
|
|
261
|
+
newStart: number;
|
|
262
|
+
lines: string[];
|
|
263
|
+
}
|
|
264
|
+
interface DiffInfo {
|
|
265
|
+
filePath: string;
|
|
266
|
+
hunks: DiffHunk[];
|
|
267
|
+
}
|
|
268
|
+
interface TokenUsage {
|
|
269
|
+
inputTokens: number;
|
|
270
|
+
outputTokens: number;
|
|
271
|
+
cost: number;
|
|
272
|
+
sessionInputTokens: number;
|
|
273
|
+
sessionOutputTokens: number;
|
|
274
|
+
sessionCost: number;
|
|
275
|
+
contextPercent?: number;
|
|
276
|
+
}
|
|
277
|
+
type ApprovalAnswer = 'allow' | 'always' | 'deny' | 'all' | 'similar';
|
|
278
|
+
interface ApprovalRequest {
|
|
279
|
+
toolName: string;
|
|
280
|
+
input: Record<string, unknown>;
|
|
281
|
+
summary: string;
|
|
282
|
+
index: number;
|
|
283
|
+
total: number;
|
|
284
|
+
diff?: DiffInfo;
|
|
285
|
+
/** Present when a bash command references a sensitive system path. */
|
|
286
|
+
warning?: string;
|
|
287
|
+
}
|
|
288
|
+
interface AgentBridgeEvents {
|
|
289
|
+
'stream-text': (text: string) => void;
|
|
290
|
+
'stream-code-block': (code: string, lang: string) => void;
|
|
291
|
+
'tool-start': (tool: ToolInfo) => void;
|
|
292
|
+
'tool-complete': (tool: ToolCompleteInfo) => void;
|
|
293
|
+
'tool-denied': (tool: {
|
|
294
|
+
name: string;
|
|
295
|
+
label: string;
|
|
296
|
+
}) => void;
|
|
297
|
+
'approval-request': (request: ApprovalRequest, respond: (answer: ApprovalAnswer) => void) => void;
|
|
298
|
+
'diff': (diff: DiffInfo) => void;
|
|
299
|
+
'usage': (usage: TokenUsage) => void;
|
|
300
|
+
'thinking-start': () => void;
|
|
301
|
+
'thinking-stop': () => void;
|
|
302
|
+
'turn-complete': () => void;
|
|
303
|
+
'error': (message: string) => void;
|
|
304
|
+
'input-request': (respond: (input: string) => void) => void;
|
|
305
|
+
}
|
|
306
|
+
type EventName = keyof AgentBridgeEvents;
|
|
307
|
+
/**
|
|
308
|
+
* Event-based bridge between the agent loop and the ink UI.
|
|
309
|
+
*
|
|
310
|
+
* The agent loop emits events (stream chunks, tool status, approval requests)
|
|
311
|
+
* and the UI subscribes to render them. Input flows back from the UI to the
|
|
312
|
+
* agent via callback functions passed in event payloads.
|
|
313
|
+
*/
|
|
314
|
+
declare class AgentBridge extends EventEmitter {
|
|
315
|
+
/** Turn-scoped flag: when true, remaining tool calls skip approval. */
|
|
316
|
+
approveAllForTurn: boolean;
|
|
317
|
+
emit<K extends EventName>(event: K, ...args: Parameters<AgentBridgeEvents[K]>): boolean;
|
|
318
|
+
on<K extends EventName>(event: K, listener: AgentBridgeEvents[K]): this;
|
|
319
|
+
once<K extends EventName>(event: K, listener: AgentBridgeEvents[K]): this;
|
|
320
|
+
off<K extends EventName>(event: K, listener: AgentBridgeEvents[K]): this;
|
|
321
|
+
/** Reset turn-scoped state. Called on 'turn-complete'. */
|
|
322
|
+
resetTurn(): void;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Append-only audit log for a single copair session.
|
|
327
|
+
*
|
|
328
|
+
* Each session produces one audit.jsonl file at:
|
|
329
|
+
* .copair/sessions/<id>/audit.jsonl
|
|
330
|
+
*
|
|
331
|
+
* Every line is a JSON-serialized AuditEntry. The file is created with mode
|
|
332
|
+
* 0o600 on first write and is append-only — existing entries are never
|
|
333
|
+
* modified or deleted by this module.
|
|
334
|
+
*
|
|
335
|
+
* input_summary is always redacted and truncated to ≤ 200 chars before
|
|
336
|
+
* writing so that raw secrets never appear in the audit log.
|
|
337
|
+
*/
|
|
338
|
+
type AuditEvent = 'session_start' | 'session_end' | 'tool_call' | 'approval' | 'denial' | 'path_block' | 'schema_rejection' | 'bash_sensitive_path';
|
|
339
|
+
type AuditOutcome = 'allowed' | 'denied' | 'error';
|
|
340
|
+
interface AuditEntry {
|
|
341
|
+
ts: string;
|
|
342
|
+
event: AuditEvent;
|
|
343
|
+
tool?: string;
|
|
344
|
+
/** Truncated (≤ 200 chars) and redacted summary of tool input. Never contains raw secrets. */
|
|
345
|
+
input_summary?: string;
|
|
346
|
+
approved_by?: 'user' | 'allow_list' | 'auto';
|
|
347
|
+
outcome: AuditOutcome;
|
|
348
|
+
detail?: string;
|
|
349
|
+
}
|
|
350
|
+
/** Input to append() — ts is added automatically; input_summary is raw (will be redacted). */
|
|
351
|
+
type AuditEntryInput = Omit<AuditEntry, 'ts'>;
|
|
352
|
+
declare class AuditLog {
|
|
353
|
+
private readonly logPath;
|
|
354
|
+
constructor(sessionDir: string);
|
|
355
|
+
/** Append one entry. input_summary is redacted and truncated before writing. */
|
|
356
|
+
append(input: AuditEntryInput): Promise<void>;
|
|
357
|
+
getLogPath(): string;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
type RiskLevel = 'safe' | 'needs-approval' | 'always-ask';
|
|
361
|
+
type GateMode = 'ask' | 'auto-approve' | 'deny';
|
|
362
|
+
declare class ApprovalGate {
|
|
363
|
+
private mode;
|
|
364
|
+
private alwaysAllow;
|
|
365
|
+
private allowList;
|
|
366
|
+
private trustedPaths;
|
|
367
|
+
private bridge;
|
|
368
|
+
private auditLog;
|
|
369
|
+
private pendingIndex;
|
|
370
|
+
private pendingTotal;
|
|
371
|
+
constructor(mode?: GateMode, allowList?: AllowList | null);
|
|
372
|
+
/** Set the bridge for ink-based approval prompts. */
|
|
373
|
+
setBridge(bridge: AgentBridge): void;
|
|
374
|
+
setAuditLog(log: AuditLog): void;
|
|
375
|
+
/** Set context for batch approval counting. */
|
|
376
|
+
setApprovalContext(index: number, total: number): void;
|
|
377
|
+
/** Register a path as trusted. File mutations under/at this path skip approval. */
|
|
378
|
+
addTrustedPath(path: string): void;
|
|
379
|
+
/** Check if a tool call targets a trusted path. Only applies to write/edit tools. */
|
|
380
|
+
isTrustedPath(toolName: string, input: Record<string, unknown>): boolean;
|
|
381
|
+
classify(toolName: string, input: Record<string, unknown>): RiskLevel;
|
|
382
|
+
/**
|
|
383
|
+
* Gate check. Called unconditionally before every tool execution.
|
|
384
|
+
* Returns true if execution may proceed, false if denied.
|
|
385
|
+
*
|
|
386
|
+
* The agent never calls this. ToolExecutor calls it. The agent only
|
|
387
|
+
* sees the resulting ExecutionResult.
|
|
388
|
+
*/
|
|
389
|
+
allow(toolName: string, input: Record<string, unknown>): Promise<boolean>;
|
|
390
|
+
/** Bridge-based approval: emit event and await response from ink UI. */
|
|
391
|
+
private bridgePrompt;
|
|
392
|
+
/** Legacy approval prompt: reads from /dev/tty directly (not stdin).
|
|
393
|
+
*
|
|
394
|
+
* @param defaultAllow When true (used for `always-ask` tools like web_search),
|
|
395
|
+
* pressing Enter without typing confirms the action. For all other tools the
|
|
396
|
+
* safe default is to deny on empty input.
|
|
397
|
+
*/
|
|
398
|
+
private legacyPrompt;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Repository boundary enforcement for all file-system tool operations.
|
|
403
|
+
*
|
|
404
|
+
* PathGuard is a session singleton instantiated once at startup and injected
|
|
405
|
+
* into ToolExecutor. All path checking is centralized there — individual tools
|
|
406
|
+
* receive an already-resolved path and never call PathGuard directly. This
|
|
407
|
+
* ensures new file tools cannot accidentally bypass the boundary check.
|
|
408
|
+
*
|
|
409
|
+
* P0 policy: all paths outside the project root are unconditionally denied.
|
|
410
|
+
* P1 policy: PathPolicy introduces an allow_paths escape hatch (subject to
|
|
411
|
+
* normal approval gate) and a deny_paths override for the built-in deny list.
|
|
412
|
+
* Paths matching the deny list are always denied, regardless of allow_paths.
|
|
413
|
+
*
|
|
414
|
+
* Check order (outside-project paths only):
|
|
415
|
+
* 1. Built-in deny list / deny_paths → hard deny
|
|
416
|
+
* 2. allow_paths glob match → allow (to approval gate)
|
|
417
|
+
* 3. Default → deny (strict) or warn+allow (warn mode)
|
|
418
|
+
*
|
|
419
|
+
* Note on .env patterns: BUILTIN_DENY includes glob patterns for .env files
|
|
420
|
+
* scoped to paths outside the project root. Paths inside the project root
|
|
421
|
+
* return at step 1 of check() before the deny list is evaluated, so .env
|
|
422
|
+
* files inside the project are subject only to the normal approval gate.
|
|
423
|
+
*/
|
|
424
|
+
type PathGuardResult = {
|
|
425
|
+
allowed: true;
|
|
426
|
+
resolvedPath: string;
|
|
427
|
+
} | {
|
|
428
|
+
allowed: false;
|
|
429
|
+
reason: 'access-denied' | 'parent-missing';
|
|
430
|
+
};
|
|
431
|
+
/**
|
|
432
|
+
* P1 policy configuration for cross-project path access.
|
|
433
|
+
* Sourced from `permissions.allow_paths` and `permissions.deny_paths` in config.
|
|
434
|
+
*/
|
|
435
|
+
interface PathPolicy {
|
|
436
|
+
/** Glob patterns of paths outside the project root that the agent may request access to. */
|
|
437
|
+
allowPaths: string[];
|
|
438
|
+
/**
|
|
439
|
+
* Glob patterns unconditionally denied regardless of approval mode or session overrides.
|
|
440
|
+
* When non-empty, replaces BUILTIN_DENY entirely. To add to the built-in list, spread
|
|
441
|
+
* BUILTIN_DENY into this array.
|
|
442
|
+
*/
|
|
443
|
+
denyPaths: string[];
|
|
444
|
+
}
|
|
445
|
+
declare class PathGuard {
|
|
446
|
+
private projectRoot;
|
|
447
|
+
private mode;
|
|
448
|
+
private expandedDenyPatterns;
|
|
449
|
+
private expandedAllowPatterns;
|
|
450
|
+
constructor(cwd: string, mode?: 'strict' | 'warn', policy?: PathPolicy);
|
|
451
|
+
/**
|
|
452
|
+
* Resolve a path and check it against the project boundary and deny/allow lists.
|
|
453
|
+
*
|
|
454
|
+
* @param rawPath The raw path string from tool input.
|
|
455
|
+
* @param mustExist true for read operations (file must exist); false for
|
|
456
|
+
* write/edit operations (parent dir must exist).
|
|
457
|
+
*/
|
|
458
|
+
check(rawPath: string, mustExist: boolean): PathGuardResult;
|
|
459
|
+
private isDenied;
|
|
460
|
+
private isAllowed;
|
|
461
|
+
/**
|
|
462
|
+
* Attempt to locate the git repository root starting from cwd.
|
|
463
|
+
* Falls back to cwd itself if not inside a git repo.
|
|
464
|
+
*
|
|
465
|
+
* Runs exactly once per session (at PathGuard construction).
|
|
466
|
+
*/
|
|
467
|
+
static findProjectRoot(cwd: string): string;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
interface ExecutionResult {
|
|
471
|
+
content: string;
|
|
472
|
+
isError?: boolean;
|
|
473
|
+
/** True when the gate blocked execution. The agent sees this as a tool error. */
|
|
474
|
+
denied?: boolean;
|
|
475
|
+
/** Actual tool execution time in ms (excludes approval prompt wait). */
|
|
476
|
+
_durationMs?: number;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Executes tools on behalf of the agent loop.
|
|
480
|
+
*
|
|
481
|
+
* This is the only path through which a tool may run. The execution order is:
|
|
482
|
+
* 1. FR-02: Zod schema validation (rejects malformed input before anything else)
|
|
483
|
+
* 2. Approval gate (unconditional — cannot be bypassed)
|
|
484
|
+
* 3. FR-03: PathGuard boundary check (centralized — individual tools never call PathGuard)
|
|
485
|
+
* 4. Tool execution
|
|
486
|
+
* 5. FR-04: Redact secrets from output before returning to agent
|
|
487
|
+
*
|
|
488
|
+
* The agent has no reference to the ApprovalGate or PathGuard and cannot
|
|
489
|
+
* influence whether these checks run.
|
|
490
|
+
*/
|
|
491
|
+
declare class ToolExecutor {
|
|
492
|
+
private readonly registry;
|
|
493
|
+
private readonly gate;
|
|
494
|
+
private readonly pathGuard;
|
|
495
|
+
private auditLog;
|
|
496
|
+
constructor(registry: ToolRegistry, gate: ApprovalGate, pathGuardOrCwd?: PathGuard | string);
|
|
497
|
+
setAuditLog(log: AuditLog): void;
|
|
498
|
+
execute(toolName: string, rawInput: Record<string, unknown>, onApproved?: () => void): Promise<ExecutionResult>;
|
|
499
|
+
/**
|
|
500
|
+
* Inspect tool input for known path fields and run each through PathGuard.
|
|
501
|
+
* Returns an error ExecutionResult if any path is denied, otherwise null.
|
|
502
|
+
* Mutates input[field] with the resolved (realpath) value on success so the
|
|
503
|
+
* tool uses a canonical path rather than a potentially traversal-containing one.
|
|
504
|
+
*
|
|
505
|
+
* Centralised here so individual tools never need to call PathGuard directly.
|
|
506
|
+
*/
|
|
507
|
+
private checkPaths;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
interface PluginContext {
|
|
511
|
+
config: CopairConfig;
|
|
512
|
+
providerRegistry: ProviderRegistry;
|
|
513
|
+
toolRegistry: ToolRegistry;
|
|
514
|
+
version: string;
|
|
515
|
+
edition: 'community' | 'pro';
|
|
516
|
+
}
|
|
517
|
+
interface PreRequestEvent {
|
|
518
|
+
messages: Message[];
|
|
519
|
+
tools: ToolDefinition$1[];
|
|
520
|
+
systemPrompt: string;
|
|
521
|
+
provider: Provider;
|
|
522
|
+
model: string;
|
|
523
|
+
/** Metadata plugins can attach — survives across hooks in the same turn */
|
|
524
|
+
meta: Record<string, unknown>;
|
|
525
|
+
}
|
|
526
|
+
interface PostRequestEvent {
|
|
527
|
+
messages: Message[];
|
|
528
|
+
response: {
|
|
529
|
+
text: string;
|
|
530
|
+
toolCalls: Array<{
|
|
531
|
+
id: string;
|
|
532
|
+
name: string;
|
|
533
|
+
input: Record<string, unknown>;
|
|
534
|
+
}>;
|
|
535
|
+
usage: {
|
|
536
|
+
inputTokens: number;
|
|
537
|
+
outputTokens: number;
|
|
538
|
+
} | null;
|
|
539
|
+
};
|
|
540
|
+
provider: Provider;
|
|
541
|
+
model: string;
|
|
542
|
+
meta: Record<string, unknown>;
|
|
543
|
+
}
|
|
544
|
+
interface ProviderInterceptEvent {
|
|
545
|
+
currentProvider: Provider;
|
|
546
|
+
model: string;
|
|
547
|
+
messages: Message[];
|
|
548
|
+
tokenCount: number;
|
|
549
|
+
}
|
|
550
|
+
interface PreToolCallEvent {
|
|
551
|
+
toolName: string;
|
|
552
|
+
input: Record<string, unknown>;
|
|
553
|
+
meta: Record<string, unknown>;
|
|
554
|
+
}
|
|
555
|
+
interface PostToolCallEvent {
|
|
556
|
+
toolName: string;
|
|
557
|
+
input: Record<string, unknown>;
|
|
558
|
+
result: ExecutionResult;
|
|
559
|
+
meta: Record<string, unknown>;
|
|
560
|
+
}
|
|
561
|
+
interface SessionEvent {
|
|
562
|
+
sessionId: string;
|
|
563
|
+
model: string;
|
|
564
|
+
config: CopairConfig;
|
|
565
|
+
type: 'create' | 'resume' | 'close';
|
|
566
|
+
}
|
|
567
|
+
interface CopairPlugin {
|
|
568
|
+
/** Unique plugin name (e.g., 'copair-pro/demo') */
|
|
569
|
+
name: string;
|
|
570
|
+
/** Plugin version (informational, not enforced) */
|
|
571
|
+
version: string;
|
|
572
|
+
/**
|
|
573
|
+
* Called once during bootstrap, after config is loaded.
|
|
574
|
+
* Use for setup: register custom providers, tools, commands.
|
|
575
|
+
*/
|
|
576
|
+
initialize?(context: PluginContext): Promise<void> | void;
|
|
577
|
+
/**
|
|
578
|
+
* Called before each LLM request.
|
|
579
|
+
* Can modify messages, tools, or swap the provider.
|
|
580
|
+
*/
|
|
581
|
+
preRequest?(event: PreRequestEvent): Promise<PreRequestEvent> | PreRequestEvent;
|
|
582
|
+
/**
|
|
583
|
+
* Called after each LLM response (full turn, after streaming completes).
|
|
584
|
+
* Read-only observation point.
|
|
585
|
+
*/
|
|
586
|
+
postRequest?(event: PostRequestEvent): Promise<void> | void;
|
|
587
|
+
/**
|
|
588
|
+
* Called before provider.chat() — can return a different provider.
|
|
589
|
+
* This is the hook for smart model switching.
|
|
590
|
+
*/
|
|
591
|
+
providerInterceptor?(event: ProviderInterceptEvent): Provider | undefined;
|
|
592
|
+
/** Called before a tool executes (after validation + approval) */
|
|
593
|
+
preToolCall?(event: PreToolCallEvent): Promise<PreToolCallEvent> | PreToolCallEvent;
|
|
594
|
+
/** Called after a tool executes */
|
|
595
|
+
postToolCall?(event: PostToolCallEvent): Promise<void> | void;
|
|
596
|
+
/** Called when a session starts or resumes */
|
|
597
|
+
sessionStart?(event: SessionEvent): Promise<void> | void;
|
|
598
|
+
/** Called when a session ends */
|
|
599
|
+
sessionEnd?(event: SessionEvent): Promise<void> | void;
|
|
600
|
+
/** Cleanup on shutdown */
|
|
601
|
+
destroy?(): Promise<void> | void;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
declare class PluginManager {
|
|
605
|
+
private plugins;
|
|
606
|
+
/**
|
|
607
|
+
* Register a plugin instance. Called during bootstrap
|
|
608
|
+
* (either from config.yaml or programmatic API).
|
|
609
|
+
*/
|
|
610
|
+
register(plugin: CopairPlugin): void;
|
|
611
|
+
/**
|
|
612
|
+
* Load plugins from config.yaml `plugins` array.
|
|
613
|
+
* Each entry is a package name or local path resolved via import().
|
|
614
|
+
*/
|
|
615
|
+
loadFromConfig(pluginPaths: string[]): Promise<void>;
|
|
616
|
+
/** Initialize all plugins (called once during bootstrap). */
|
|
617
|
+
initialize(context: PluginContext): Promise<void>;
|
|
618
|
+
/**
|
|
619
|
+
* Run preRequest hooks in registration order.
|
|
620
|
+
* Each plugin receives the (possibly modified) event from the prior plugin.
|
|
621
|
+
*/
|
|
622
|
+
preRequest(event: PreRequestEvent): Promise<PreRequestEvent>;
|
|
623
|
+
/** Run postRequest hooks (observation only). */
|
|
624
|
+
postRequest(event: PostRequestEvent): Promise<void>;
|
|
625
|
+
/**
|
|
626
|
+
* Run providerInterceptor hooks. First plugin to return
|
|
627
|
+
* a non-undefined provider wins (short-circuit).
|
|
628
|
+
*/
|
|
629
|
+
interceptProvider(event: ProviderInterceptEvent): Provider;
|
|
630
|
+
preToolCall(event: PreToolCallEvent): Promise<PreToolCallEvent>;
|
|
631
|
+
postToolCall(event: PostToolCallEvent): Promise<void>;
|
|
632
|
+
sessionStart(event: SessionEvent): Promise<void>;
|
|
633
|
+
sessionEnd(event: SessionEvent): Promise<void>;
|
|
634
|
+
/** Destroy all plugins on shutdown. */
|
|
635
|
+
destroy(): Promise<void>;
|
|
636
|
+
/** Get the count of registered plugins. */
|
|
637
|
+
get count(): number;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
declare class ConversationManager {
|
|
641
|
+
private messages;
|
|
642
|
+
append(role: Message['role'], content: ContentBlock[]): void;
|
|
643
|
+
appendText(role: Message['role'], text: string): void;
|
|
644
|
+
getHistory(): Message[];
|
|
645
|
+
clear(): void;
|
|
646
|
+
get length(): number;
|
|
647
|
+
toJSONL(): string;
|
|
648
|
+
static fromJSONL(data: string): Message[];
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
type FormatName = 'dsml' | 'qwen-xml' | 'fenced-block';
|
|
652
|
+
|
|
653
|
+
interface AgentOptions {
|
|
654
|
+
systemPrompt?: string;
|
|
655
|
+
maxTokens?: number;
|
|
656
|
+
temperature?: number;
|
|
657
|
+
toolCallFormat?: FormatName;
|
|
658
|
+
bridge?: AgentBridge;
|
|
659
|
+
pluginManager?: PluginManager;
|
|
660
|
+
}
|
|
661
|
+
declare class Agent {
|
|
662
|
+
private provider;
|
|
663
|
+
private toolRegistry;
|
|
664
|
+
private executor;
|
|
665
|
+
private conversation;
|
|
666
|
+
private contextWindow;
|
|
667
|
+
private renderer;
|
|
668
|
+
private options;
|
|
669
|
+
private _model;
|
|
670
|
+
private formatter;
|
|
671
|
+
private textFilter;
|
|
672
|
+
private pluginManager?;
|
|
673
|
+
constructor(provider: Provider, model: string, toolRegistry: ToolRegistry, executor: ToolExecutor, options?: AgentOptions);
|
|
674
|
+
get model(): string;
|
|
675
|
+
getConversation(): ConversationManager;
|
|
676
|
+
/**
|
|
677
|
+
* Switch to a new provider/model mid-session.
|
|
678
|
+
* Summarizes the conversation using the current model, then reinitializes.
|
|
679
|
+
*/
|
|
680
|
+
switchModel(newProvider: Provider, newModel: string): Promise<void>;
|
|
681
|
+
handleMessage(userInput: string): Promise<{
|
|
682
|
+
usage: {
|
|
683
|
+
inputTokens: number;
|
|
684
|
+
outputTokens: number;
|
|
685
|
+
} | null;
|
|
686
|
+
/** Input tokens from the last API call — reflects actual context window usage. */
|
|
687
|
+
lastInputTokens: number;
|
|
688
|
+
}>;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
interface SessionMetadata {
|
|
692
|
+
id: string;
|
|
693
|
+
identifier: string;
|
|
694
|
+
model: string;
|
|
695
|
+
created: string;
|
|
696
|
+
lastActive: string;
|
|
697
|
+
messageCount: number;
|
|
698
|
+
hasSummary: boolean;
|
|
699
|
+
branch?: string;
|
|
700
|
+
identifierDerived?: boolean;
|
|
701
|
+
}
|
|
702
|
+
declare class SessionManager {
|
|
703
|
+
private metadata;
|
|
704
|
+
private sessionDir;
|
|
705
|
+
private sessionsDir;
|
|
706
|
+
private saveOffset;
|
|
707
|
+
private projectRoot;
|
|
708
|
+
constructor(projectRoot: string);
|
|
709
|
+
create(model: string, branch?: string): Promise<SessionMetadata>;
|
|
710
|
+
save(messages: Message[]): Promise<void>;
|
|
711
|
+
resume(sessionId: string): Promise<{
|
|
712
|
+
metadata: SessionMetadata;
|
|
713
|
+
messages: Message[];
|
|
714
|
+
summary: string | null;
|
|
715
|
+
}>;
|
|
716
|
+
close(messages?: Message[], summarizer?: {
|
|
717
|
+
summarize(messages: Message[]): Promise<string | null>;
|
|
718
|
+
}): Promise<void>;
|
|
719
|
+
updateIdentifier(identifier: string): void;
|
|
720
|
+
rename(newName: string): void;
|
|
721
|
+
getMetadata(): SessionMetadata | null;
|
|
722
|
+
getSessionDir(): string;
|
|
723
|
+
static listSessions(sessionsDir: string): Promise<SessionMetadata[]>;
|
|
724
|
+
static deleteSession(sessionsDir: string, sessionId: string): Promise<void>;
|
|
725
|
+
static migrateGlobalRecovery(sessionsDir: string, projectRoot: string): Promise<SessionMetadata | null>;
|
|
726
|
+
static cleanup(sessionsDir: string, maxSessions: number): Promise<void>;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
declare function getVersionString(options?: BootstrapOptions): string;
|
|
730
|
+
interface BootstrapOptions {
|
|
731
|
+
edition?: 'community' | 'pro';
|
|
732
|
+
editionVersion?: string;
|
|
733
|
+
plugins?: CopairPlugin[];
|
|
734
|
+
argv?: string[];
|
|
735
|
+
}
|
|
736
|
+
declare function bootstrapCLI(options?: BootstrapOptions): Promise<void>;
|
|
737
|
+
|
|
738
|
+
export { Agent, type AgentOptions, ApprovalGate, type BootstrapOptions, type CopairConfig, type CopairPlugin, type Message, type PluginContext, PluginManager, type PostRequestEvent, type PostToolCallEvent, type PreRequestEvent, type PreToolCallEvent, type Provider, type ProviderInterceptEvent, type ProviderOptions, ProviderRegistry, type SessionEvent, SessionManager, type StreamChunk, type Tool, type ToolDefinition$1 as ToolDefinition, ToolExecutor, ToolRegistry, type ToolResult, bootstrapCLI, getVersionString };
|