opencode-ai-cli 1.17.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/GEMINI.md +11 -0
- package/cli.ts +17 -0
- package/commands/agent.ts +102 -0
- package/commands/chat.ts +518 -0
- package/commands/models.ts +10 -0
- package/commands/providers/index.ts +10 -0
- package/commands/providers/login.ts +30 -0
- package/commands/providers/logout.ts +13 -0
- package/commands/providers/setProvider.ts +9 -0
- package/package.json +22 -0
- package/src/core/auth-storage.ts +136 -0
- package/src/core/model-registry.ts +23 -0
- package/src/engine/agentLoop.ts +389 -0
- package/src/engine/messages.ts +110 -0
- package/src/engine/systemPrompt.ts +58 -0
- package/src/engine/type.ts +133 -0
- package/src/providers/gemini.ts +122 -0
- package/src/providers/openai.ts +60 -0
- package/src/subagent/README.md +177 -0
- package/src/subagent/agents/planner.md +37 -0
- package/src/subagent/agents/reviewer.md +35 -0
- package/src/subagent/agents/scout.md +49 -0
- package/src/subagent/agents/worker.md +29 -0
- package/src/subagent/agents.ts +89 -0
- package/src/subagent/index.ts +224 -0
- package/src/subagent/prompts/implement-and-review.md +10 -0
- package/src/subagent/prompts/implement.md +10 -0
- package/src/subagent/prompts/scout-and-plan.md +9 -0
- package/src/tools/bash-tool.ts +44 -0
- package/src/tools/edit-tool.ts +85 -0
- package/src/tools/find-tool.ts +81 -0
- package/src/tools/grep-tool.ts +100 -0
- package/src/tools/index.ts +37 -0
- package/src/tools/ls-tool.ts +93 -0
- package/src/tools/plan-tool.ts +35 -0
- package/src/tools/read-tool.ts +89 -0
- package/src/tools/truncate.ts +21 -0
- package/src/tools/weather-tool.ts +55 -0
- package/src/tools/write-tool.ts +53 -0
- package/src/types.ts +28 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ProviderName } from "../core/auth-storage";
|
|
2
|
+
import { AgentMessage, AssistantMessage, ToolResultMessage } from "./type";
|
|
3
|
+
|
|
4
|
+
export function convertToLlm(
|
|
5
|
+
messages: AgentMessage[],
|
|
6
|
+
provider: ProviderName,
|
|
7
|
+
) {
|
|
8
|
+
if (provider === "openai") {
|
|
9
|
+
return convertToOpenAi(messages);
|
|
10
|
+
} else if (provider === "gemini") {
|
|
11
|
+
return messages;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
throw new Error(`Provider ${provider} not supported yet`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function convertToOpenAi(messages: AgentMessage[]): any[] {
|
|
18
|
+
return messages.map((msg) => {
|
|
19
|
+
switch (msg.role) {
|
|
20
|
+
case "system":
|
|
21
|
+
case "user":
|
|
22
|
+
return { role: msg.role, content: msg.content };
|
|
23
|
+
|
|
24
|
+
case "assistant": {
|
|
25
|
+
const textContent = msg.content
|
|
26
|
+
.filter((part) => part.type === "text")
|
|
27
|
+
.map((part: any) => part.text)
|
|
28
|
+
.join("\n");
|
|
29
|
+
|
|
30
|
+
const toolCalls = msg.content
|
|
31
|
+
.filter((part) => part.type === "toolCall")
|
|
32
|
+
.map((part: any) => part.toolCall);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
role: "assistant",
|
|
36
|
+
content: textContent || null,
|
|
37
|
+
tool_calls:
|
|
38
|
+
toolCalls.length > 0
|
|
39
|
+
? toolCalls.map((tc: any) => ({
|
|
40
|
+
id: tc.id,
|
|
41
|
+
type: "function",
|
|
42
|
+
function: {
|
|
43
|
+
name: tc.name,
|
|
44
|
+
arguments: JSON.stringify(tc.arguments),
|
|
45
|
+
},
|
|
46
|
+
}))
|
|
47
|
+
: undefined,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
case "tool":
|
|
52
|
+
return {
|
|
53
|
+
role: "tool",
|
|
54
|
+
tool_call_id: msg.toolCallId,
|
|
55
|
+
content: msg.content,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
default:
|
|
59
|
+
const _exhaustiveCheck: never = msg;
|
|
60
|
+
return _exhaustiveCheck;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function convertToGeminiAi(messages: AgentMessage[]): any[] {
|
|
66
|
+
return messages.map((msg) => {
|
|
67
|
+
switch (msg.role) {
|
|
68
|
+
case "system":
|
|
69
|
+
return msg;
|
|
70
|
+
|
|
71
|
+
case "user":
|
|
72
|
+
return { role: "user", parts: [{ text: msg.content }] };
|
|
73
|
+
|
|
74
|
+
case "assistant": {
|
|
75
|
+
const parts = msg.content.map((part) => {
|
|
76
|
+
if (part.type === "text") {
|
|
77
|
+
return { text: part.text };
|
|
78
|
+
} else if (part.type === "toolCall") {
|
|
79
|
+
return {
|
|
80
|
+
functionCall: {
|
|
81
|
+
name: part.toolCall.name,
|
|
82
|
+
args: part.toolCall.arguments,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
} else if (part.type === "thinking") {
|
|
86
|
+
return { text: `Thinking: ${part.thinking}` };
|
|
87
|
+
}
|
|
88
|
+
return { text: "" };
|
|
89
|
+
});
|
|
90
|
+
return { role: "model", parts };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
case "tool":
|
|
94
|
+
return {
|
|
95
|
+
role: "user", // Gemini uses 'user' role for function responses in some SDKs, or 'function'
|
|
96
|
+
parts: [
|
|
97
|
+
{
|
|
98
|
+
functionResponse: {
|
|
99
|
+
name: msg.name,
|
|
100
|
+
response: { content: msg.content },
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
default:
|
|
107
|
+
return { role: "user", parts: [{ text: "" }] };
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function buildSystemPrompt(): string {
|
|
2
|
+
const cwd = process.cwd();
|
|
3
|
+
const date = new Date().toISOString().split("T")[0];
|
|
4
|
+
|
|
5
|
+
return `
|
|
6
|
+
You are a senior software engineering agent operating inside a user's terminal.
|
|
7
|
+
|
|
8
|
+
Current Date: ${date}
|
|
9
|
+
Current Working Directory: ${cwd}
|
|
10
|
+
|
|
11
|
+
# CRITICAL RESEARCH MANDATE
|
|
12
|
+
|
|
13
|
+
You are strictly forbidden from guessing, assuming, or hallucinating information about the user's project, files, or code.
|
|
14
|
+
|
|
15
|
+
# THE SCOUT AND PLAN PIPELINE (MANDATORY)
|
|
16
|
+
|
|
17
|
+
You are the Orchestrator. When the user asks a question about their project, repository, architecture, or asks you to implement a feature, fix a bug, or execute a change, you MUST follow this strict 2-step pipeline:
|
|
18
|
+
|
|
19
|
+
1. THE SCOUT PHASE:
|
|
20
|
+
You MUST immediately call the 'subagent' tool using the PARALLEL mode by passing an array of 'tasks'.
|
|
21
|
+
Spawn 2 or 3 'scout' agents concurrently to map different parts of the project at the exact same time. This drastically speeds up research.
|
|
22
|
+
Example:
|
|
23
|
+
tasks: [
|
|
24
|
+
{ agent: "scout", task: "Read package.json, configs, and find the main entry points." },
|
|
25
|
+
{ agent: "scout", task: "Analyze the core logic in the src directory." },
|
|
26
|
+
{ agent: "scout", task: "Investigate any specific bugs or files mentioned in the user's prompt: <user prompt>" }
|
|
27
|
+
]
|
|
28
|
+
DO NOT use 'ls', 'read', 'grep', or 'find' yourself. You must delegate research to the scouts.
|
|
29
|
+
DO NOT guess the project structure. DO NOT call 'propose_plan' yet.
|
|
30
|
+
YOU MAY ONLY CALL THE SUBAGENT TOOL ONCE. Launch all your scouts in that single call.
|
|
31
|
+
|
|
32
|
+
2. THE PLAN PHASE:
|
|
33
|
+
Only AFTER you receive the Markdown report from the scout subagent, you may call 'propose_plan'. Use the "Start Here" and "Architecture" sections of the scout report to formulate your step-by-step plan.
|
|
34
|
+
The plan must separate read-only investigation from mutating work. Ask for approval before any edit, write, delete, or command that can change project state.
|
|
35
|
+
|
|
36
|
+
3. THE IMPLEMENTATION PHASE:
|
|
37
|
+
Once the user approves your plan, you are encouraged to use 'read_file', 'grep', 'edit_file', and 'bash' DIRECTLY to execute the steps. You do not need to call subagents for simple file reads or edits during this phase.
|
|
38
|
+
|
|
39
|
+
Calling 'propose_plan' or trying to change files before invoking the scout is strictly forbidden.
|
|
40
|
+
|
|
41
|
+
You operate under an "Evidence-First" policy.
|
|
42
|
+
|
|
43
|
+
Use tools only when they are needed to answer the user's request. For casual
|
|
44
|
+
conversation, greetings, thanks, or simple chat answer directly without using tools.
|
|
45
|
+
|
|
46
|
+
Rules:
|
|
47
|
+
- Never claim a file exists unless your scout has verified it.
|
|
48
|
+
- Never claim code behaves a certain way unless your scout has inspected it.
|
|
49
|
+
- Never invent dependencies, configurations, scripts, or APIs.
|
|
50
|
+
- MANDATORY: When using mutating tools (\`write_file\`, \`edit_file\`, \`bash\`), you MUST explicitly describe your action.
|
|
51
|
+
- For \`edit_file\`, clearly state what is being removed and what is being added, and identify the affected lines.
|
|
52
|
+
- For \`write_file\`, state that you are creating/overwriting a file at a specific path.
|
|
53
|
+
- For \`bash\`, state exactly what command you are running and its purpose.
|
|
54
|
+
|
|
55
|
+
If the answer cannot be determined from available evidence, respond:
|
|
56
|
+
"I searched the repository using the scout but could not find enough evidence."
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { AgentTool, AgentToolResult } from "../types";
|
|
2
|
+
|
|
3
|
+
export type MessagePart =
|
|
4
|
+
| { type: "text"; text: string }
|
|
5
|
+
| { type: "toolCall"; toolCall: AgentToolCall }
|
|
6
|
+
| { type: "thinking"; thinking: string };
|
|
7
|
+
|
|
8
|
+
export interface AgentToolCall {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
arguments: any;
|
|
12
|
+
metadata?: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UserMessage {
|
|
16
|
+
role: "user";
|
|
17
|
+
content: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SystemMessage {
|
|
21
|
+
role: "system";
|
|
22
|
+
content: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AssistantMessage {
|
|
26
|
+
role: "assistant";
|
|
27
|
+
content: MessagePart[];
|
|
28
|
+
toolCalls?: AgentToolCall[];
|
|
29
|
+
stopReason?: "stop" | "tool_calls" | "error" | "aborted";
|
|
30
|
+
metadata?: any;
|
|
31
|
+
usage?: {
|
|
32
|
+
promptTokens: number;
|
|
33
|
+
completionTokens: number;
|
|
34
|
+
totalTokens: number;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ToolResultMessage {
|
|
39
|
+
role: "tool";
|
|
40
|
+
toolCallId: string;
|
|
41
|
+
name: string;
|
|
42
|
+
content: string;
|
|
43
|
+
isError?: boolean;
|
|
44
|
+
terminate?: boolean;
|
|
45
|
+
metadata?: any;
|
|
46
|
+
usage?: {
|
|
47
|
+
promptTokens: number;
|
|
48
|
+
completionTokens: number;
|
|
49
|
+
totalTokens: number;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type AgentMessage =
|
|
54
|
+
| SystemMessage
|
|
55
|
+
| UserMessage
|
|
56
|
+
| AssistantMessage
|
|
57
|
+
| ToolResultMessage;
|
|
58
|
+
|
|
59
|
+
export interface AgentContext {
|
|
60
|
+
cwd: string;
|
|
61
|
+
messages: AgentMessage[];
|
|
62
|
+
activeToolNames: string[];
|
|
63
|
+
systemPrompt?: string;
|
|
64
|
+
tools?: AgentTool[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface BeforeToolCallContext {
|
|
68
|
+
assistantMessage: AssistantMessage;
|
|
69
|
+
toolCall: AgentToolCall;
|
|
70
|
+
context: AgentContext;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface AfterToolCallContext {
|
|
74
|
+
assistantMessage: AssistantMessage;
|
|
75
|
+
toolCall: AgentToolCall;
|
|
76
|
+
result: AgentToolResult;
|
|
77
|
+
isError: boolean;
|
|
78
|
+
context: AgentContext;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ShouldStopAfterTurnContext {
|
|
82
|
+
message: AssistantMessage;
|
|
83
|
+
toolResults: ToolResultMessage[];
|
|
84
|
+
context: AgentContext;
|
|
85
|
+
newMessages: AgentMessage[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
import { ProviderName } from "../core/auth-storage";
|
|
89
|
+
|
|
90
|
+
export interface AgentLoopConfig {
|
|
91
|
+
provider: ProviderName;
|
|
92
|
+
apiKey: string;
|
|
93
|
+
model?: string;
|
|
94
|
+
maxTurns?: number;
|
|
95
|
+
toolExecution?: "sequential" | "parallel";
|
|
96
|
+
beforeToolCall?: (
|
|
97
|
+
ctx: BeforeToolCallContext,
|
|
98
|
+
) => Promise<{ block?: boolean; reason?: string } | void>;
|
|
99
|
+
shouldStopAfterTurn?: (ctx: ShouldStopAfterTurnContext) => Promise<boolean>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export type AgentEvent =
|
|
103
|
+
| { type: "agent_start" }
|
|
104
|
+
| { type: "agent_end"; message: AgentMessage[] }
|
|
105
|
+
| { type: "turn_start" }
|
|
106
|
+
| {
|
|
107
|
+
type: "turn_end";
|
|
108
|
+
message: AssistantMessage;
|
|
109
|
+
toolResults: ToolResultMessage[];
|
|
110
|
+
}
|
|
111
|
+
| { type: "message_start"; message: AgentMessage }
|
|
112
|
+
| { type: "message_update"; message: AssistantMessage }
|
|
113
|
+
| { type: "message_end"; message: AgentMessage }
|
|
114
|
+
| {
|
|
115
|
+
type: "tool_execution_start";
|
|
116
|
+
toolCallId: string;
|
|
117
|
+
toolName: string;
|
|
118
|
+
args: any;
|
|
119
|
+
}
|
|
120
|
+
| {
|
|
121
|
+
type: "tool_execution_end";
|
|
122
|
+
toolCallId: string;
|
|
123
|
+
toolName: string;
|
|
124
|
+
result: AgentToolResult;
|
|
125
|
+
isError: boolean;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export type AgentEventSink = (event: AgentEvent) => Promise<void> | void;
|
|
129
|
+
|
|
130
|
+
export type ExecutedToolCallBatch = {
|
|
131
|
+
messages: ToolResultMessage[];
|
|
132
|
+
terminate: boolean;
|
|
133
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { ChatRequest, ChatResponse } from "../core/model-registry";
|
|
2
|
+
|
|
3
|
+
function toGeminiContents(history: any[]) {
|
|
4
|
+
return history
|
|
5
|
+
.filter((message) => message.role !== "system")
|
|
6
|
+
.map((message) => {
|
|
7
|
+
if (message.role === "assistant") {
|
|
8
|
+
if (message.metadata?.geminiParts) {
|
|
9
|
+
return {
|
|
10
|
+
role: "model",
|
|
11
|
+
parts: message.metadata.geminiParts,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const parts = message.content.map((part: any) => {
|
|
16
|
+
if (part.type === "text") {
|
|
17
|
+
return { text: part.text };
|
|
18
|
+
}
|
|
19
|
+
if (part.type === "toolCall") {
|
|
20
|
+
return {
|
|
21
|
+
functionCall: {
|
|
22
|
+
name: part.toolCall.name,
|
|
23
|
+
args: part.toolCall.arguments,
|
|
24
|
+
...part.toolCall.metadata,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return { text: "" };
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
role: "model",
|
|
32
|
+
parts: parts,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (message.role === "tool") {
|
|
37
|
+
return {
|
|
38
|
+
role: "user",
|
|
39
|
+
parts: [
|
|
40
|
+
{
|
|
41
|
+
functionResponse: {
|
|
42
|
+
name: message.name,
|
|
43
|
+
response: { result: message.content },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
role: "user",
|
|
52
|
+
parts: [{ text: message.content ?? "" }],
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function executeGemini(req: ChatRequest): Promise<ChatResponse> {
|
|
58
|
+
const systemMessage = req.history.find(
|
|
59
|
+
(message) => message.role === "system",
|
|
60
|
+
);
|
|
61
|
+
const tools = req.tools.map((tool) => tool.schema.function);
|
|
62
|
+
|
|
63
|
+
const modelToUse = req.model || "gemini-3-flash-preview";
|
|
64
|
+
|
|
65
|
+
const response = await fetch(
|
|
66
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${modelToUse}:generateContent`,
|
|
67
|
+
{
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: {
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
"x-goog-api-key": req.apiKey,
|
|
72
|
+
},
|
|
73
|
+
body: JSON.stringify({
|
|
74
|
+
systemInstruction: systemMessage
|
|
75
|
+
? { parts: [{ text: systemMessage.content }] }
|
|
76
|
+
: undefined,
|
|
77
|
+
contents: toGeminiContents(req.history),
|
|
78
|
+
tools: tools.length > 0 ? [{ functionDeclarations: tools }] : undefined,
|
|
79
|
+
}),
|
|
80
|
+
signal: req.signal,
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
const errorData = await response.json().catch(() => null);
|
|
86
|
+
throw new Error(`Gemini API Error: ${response.statusText}\nDetails: ${JSON.stringify(errorData, null, 2)}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const data = await response.json();
|
|
90
|
+
const parts = data.candidates?.[0]?.content?.parts ?? [];
|
|
91
|
+
const text = parts
|
|
92
|
+
.filter((part: any) => typeof part.text === "string")
|
|
93
|
+
.map((part: any) => part.text)
|
|
94
|
+
.join("");
|
|
95
|
+
const functionCalls = parts.filter((part: any) => part.functionCall);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
text: text || null,
|
|
99
|
+
toolCalls:
|
|
100
|
+
functionCalls.length > 0
|
|
101
|
+
? functionCalls.map((part: any, index: number) => {
|
|
102
|
+
const { name, args, ...rest } = part.functionCall;
|
|
103
|
+
return {
|
|
104
|
+
id: `gemini-tool-${index}`,
|
|
105
|
+
name: name,
|
|
106
|
+
arguments: args ?? {},
|
|
107
|
+
metadata: rest,
|
|
108
|
+
};
|
|
109
|
+
})
|
|
110
|
+
: null,
|
|
111
|
+
rawMessage: {
|
|
112
|
+
role: "assistant",
|
|
113
|
+
content: text || null,
|
|
114
|
+
geminiParts: parts,
|
|
115
|
+
},
|
|
116
|
+
usage: data.usageMetadata ? {
|
|
117
|
+
promptTokens: data.usageMetadata.promptTokenCount,
|
|
118
|
+
completionTokens: data.usageMetadata.candidatesTokenCount,
|
|
119
|
+
totalTokens: data.usageMetadata.totalTokenCount,
|
|
120
|
+
} : undefined,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ChatRequest, ChatResponse } from "../core/model-registry";
|
|
2
|
+
|
|
3
|
+
export async function executeOpenAI(req: ChatRequest): Promise<ChatResponse> {
|
|
4
|
+
const openaiTools = req.tools.map((t) => t.schema);
|
|
5
|
+
|
|
6
|
+
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
7
|
+
method: "POST",
|
|
8
|
+
headers: {
|
|
9
|
+
"Content-Type": "application/json",
|
|
10
|
+
Authorization: `Bearer ${req.apiKey}`,
|
|
11
|
+
},
|
|
12
|
+
body: JSON.stringify({
|
|
13
|
+
model: req.model || "gpt-4o-mini",
|
|
14
|
+
messages: req.history,
|
|
15
|
+
tools: openaiTools.length > 0 ? openaiTools : undefined,
|
|
16
|
+
tool_choice: openaiTools.length > 0 ? "auto" : undefined,
|
|
17
|
+
}),
|
|
18
|
+
signal: req.signal,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
const errorData = await response.json();
|
|
23
|
+
throw new Error(`OpenAI API Error: ${response.statusText}\nDetails: ${JSON.stringify(errorData, null, 2)}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const data = await response.json();
|
|
27
|
+
const assistantMessage = data.choices[0].message;
|
|
28
|
+
|
|
29
|
+
let toolCalls = null;
|
|
30
|
+
|
|
31
|
+
if (assistantMessage.tool_calls) {
|
|
32
|
+
toolCalls = assistantMessage.tool_calls.map((tc: any) => {
|
|
33
|
+
try {
|
|
34
|
+
return {
|
|
35
|
+
id: tc.id,
|
|
36
|
+
name: tc.function.name,
|
|
37
|
+
arguments: JSON.parse(tc.function.arguments),
|
|
38
|
+
};
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.error("Failed to parse tool arguments:", tc.function.arguments);
|
|
41
|
+
return {
|
|
42
|
+
id: tc.id,
|
|
43
|
+
name: tc.function.name,
|
|
44
|
+
arguments: {}, // Fallback to empty object
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
text: assistantMessage.content || null,
|
|
52
|
+
toolCalls: toolCalls,
|
|
53
|
+
rawMessage: assistantMessage,
|
|
54
|
+
usage: data.usage ? {
|
|
55
|
+
promptTokens: data.usage.prompt_tokens,
|
|
56
|
+
completionTokens: data.usage.completion_tokens,
|
|
57
|
+
totalTokens: data.usage.total_tokens,
|
|
58
|
+
} : undefined,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Subagent Example
|
|
2
|
+
|
|
3
|
+
Delegate tasks to specialized subagents with isolated context windows.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Isolated context**: Each subagent runs in a separate `pi` process
|
|
8
|
+
- **Streaming output**: See tool calls and progress as they happen
|
|
9
|
+
- **Parallel streaming**: All parallel tasks stream updates simultaneously
|
|
10
|
+
- **Markdown rendering**: Final output rendered with proper formatting (expanded view)
|
|
11
|
+
- **Usage tracking**: Shows turns, tokens, cost, and context usage per agent
|
|
12
|
+
- **Abort support**: Ctrl+C propagates to kill subagent processes
|
|
13
|
+
|
|
14
|
+
## Structure
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
subagent/
|
|
18
|
+
├── README.md # This file
|
|
19
|
+
├── index.ts # The extension (entry point)
|
|
20
|
+
├── agents.ts # Agent discovery logic
|
|
21
|
+
├── agents/ # Sample agent definitions
|
|
22
|
+
│ ├──
|
|
23
|
+
|
|
24
|
+
# Fast recon, returns compressed context
|
|
25
|
+
│ ├── planner.md # Creates implementation plans
|
|
26
|
+
│ ├── reviewer.md # Code review
|
|
27
|
+
│ └── worker.md # General-purpose (full capabilities)
|
|
28
|
+
└── prompts/ # Workflow presets (prompt templates)
|
|
29
|
+
├── implement.md # scout -> planner -> worker
|
|
30
|
+
├── scout-and-plan.md # scout -> planner (no implementation)
|
|
31
|
+
└── implement-and-review.md # worker -> reviewer -> worker
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
From the repository root, symlink the files:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Symlink the extension (must be in a subdirectory with index.ts)
|
|
40
|
+
mkdir -p ~/.pi/agent/extensions/subagent
|
|
41
|
+
ln -sf "$(pwd)/packages/coding-agent/examples/extensions/subagent/index.ts" ~/.pi/agent/extensions/subagent/index.ts
|
|
42
|
+
ln -sf "$(pwd)/packages/coding-agent/examples/extensions/subagent/agents.ts" ~/.pi/agent/extensions/subagent/agents.ts
|
|
43
|
+
|
|
44
|
+
# Symlink agents
|
|
45
|
+
mkdir -p ~/.pi/agent/agents
|
|
46
|
+
for f in packages/coding-agent/examples/extensions/subagent/agents/*.md; do
|
|
47
|
+
ln -sf "$(pwd)/$f" ~/.pi/agent/agents/$(basename "$f")
|
|
48
|
+
done
|
|
49
|
+
|
|
50
|
+
# Symlink workflow prompts
|
|
51
|
+
mkdir -p ~/.pi/agent/prompts
|
|
52
|
+
for f in packages/coding-agent/examples/extensions/subagent/prompts/*.md; do
|
|
53
|
+
ln -sf "$(pwd)/$f" ~/.pi/agent/prompts/$(basename "$f")
|
|
54
|
+
done
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Security Model
|
|
58
|
+
|
|
59
|
+
This tool executes a separate `pi` subprocess with a delegated system prompt and tool/model configuration.
|
|
60
|
+
|
|
61
|
+
**Project-local agents** (`.pi/agents/*.md`) are repo-controlled prompts that can instruct the model to read files, run bash commands, etc.
|
|
62
|
+
|
|
63
|
+
**Default behavior:** Only loads **user-level agents** from `~/.pi/agent/agents`.
|
|
64
|
+
|
|
65
|
+
To enable project-local agents, pass `agentScope: "both"` (or `"project"`). Only do this for repositories you trust.
|
|
66
|
+
|
|
67
|
+
When running interactively, the tool prompts for confirmation before running project-local agents. Set `confirmProjectAgents: false` to disable.
|
|
68
|
+
|
|
69
|
+
## Usage
|
|
70
|
+
|
|
71
|
+
### Single agent
|
|
72
|
+
```
|
|
73
|
+
Use scout to find all authentication code
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Parallel execution
|
|
77
|
+
```
|
|
78
|
+
Run 2 scouts in parallel: one to find models, one to find providers
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Chained workflow
|
|
82
|
+
```
|
|
83
|
+
Use a chain: first have scout find the read tool, then have planner suggest improvements
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Workflow prompts
|
|
87
|
+
```
|
|
88
|
+
/implement add Redis caching to the session store
|
|
89
|
+
/scout-and-plan refactor auth to support OAuth
|
|
90
|
+
/implement-and-review add input validation to API endpoints
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Tool Modes
|
|
94
|
+
|
|
95
|
+
| Mode | Parameter | Description |
|
|
96
|
+
|------|-----------|-------------|
|
|
97
|
+
| Single | `{ agent, task }` | One agent, one task |
|
|
98
|
+
| Parallel | `{ tasks: [...] }` | Multiple agents run concurrently (max 8, 4 concurrent) |
|
|
99
|
+
| Chain | `{ chain: [...] }` | Sequential with `{previous}` placeholder |
|
|
100
|
+
|
|
101
|
+
## Output Display
|
|
102
|
+
|
|
103
|
+
**Collapsed view** (default):
|
|
104
|
+
- Status icon (✓/✗/⏳) and agent name
|
|
105
|
+
- Last 5-10 items (tool calls and text)
|
|
106
|
+
- Usage stats: `3 turns ↑input ↓output RcacheRead WcacheWrite $cost ctx:contextTokens model`
|
|
107
|
+
|
|
108
|
+
**Expanded view** (Ctrl+O):
|
|
109
|
+
- Full task text
|
|
110
|
+
- All tool calls with formatted arguments
|
|
111
|
+
- Final output rendered as Markdown
|
|
112
|
+
- Per-task usage (for chain/parallel)
|
|
113
|
+
|
|
114
|
+
**Parallel mode streaming**:
|
|
115
|
+
- Shows all tasks with live status (⏳ running, ✓ done, ✗ failed)
|
|
116
|
+
- Updates as each task makes progress
|
|
117
|
+
- Shows "2/3 done, 1 running" status
|
|
118
|
+
- Returns each completed task's final output to the parent model, capped at 50 KB per task
|
|
119
|
+
- Returns failure diagnostics from stderr/error messages when a child exits before producing output
|
|
120
|
+
|
|
121
|
+
**Tool call formatting** (mimics built-in tools):
|
|
122
|
+
- `$ command` for bash
|
|
123
|
+
- `read ~/path:1-10` for read
|
|
124
|
+
- `grep /pattern/ in ~/path` for grep
|
|
125
|
+
- etc.
|
|
126
|
+
|
|
127
|
+
## Agent Definitions
|
|
128
|
+
|
|
129
|
+
Agents are markdown files with YAML frontmatter:
|
|
130
|
+
|
|
131
|
+
```markdown
|
|
132
|
+
---
|
|
133
|
+
name: my-agent
|
|
134
|
+
description: What this agent does
|
|
135
|
+
tools: read, grep, find, ls
|
|
136
|
+
model: claude-haiku-4-5
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
System prompt for the agent goes here.
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Locations:**
|
|
143
|
+
- `~/.pi/agent/agents/*.md` - User-level (always loaded)
|
|
144
|
+
- `.pi/agents/*.md` - Project-level (only with `agentScope: "project"` or `"both"`)
|
|
145
|
+
|
|
146
|
+
Project agents override user agents with the same name when `agentScope: "both"`.
|
|
147
|
+
|
|
148
|
+
## Sample Agents
|
|
149
|
+
|
|
150
|
+
| Agent | Purpose | Model | Tools |
|
|
151
|
+
|-------|---------|-------|-------|
|
|
152
|
+
| `scout` | Fast codebase recon | Haiku | read, grep, find, ls, bash |
|
|
153
|
+
| `planner` | Implementation plans | Sonnet | read, grep, find, ls |
|
|
154
|
+
| `reviewer` | Code review | Sonnet | read, grep, find, ls, bash |
|
|
155
|
+
| `worker` | General-purpose | Sonnet | (all default) |
|
|
156
|
+
|
|
157
|
+
## Workflow Prompts
|
|
158
|
+
|
|
159
|
+
| Prompt | Flow |
|
|
160
|
+
|--------|------|
|
|
161
|
+
| `/implement <query>` | scout → planner → worker |
|
|
162
|
+
| `/scout-and-plan <query>` | scout → planner |
|
|
163
|
+
| `/implement-and-review <query>` | worker → reviewer → worker |
|
|
164
|
+
|
|
165
|
+
## Error Handling
|
|
166
|
+
|
|
167
|
+
- **Exit code != 0**: Tool returns error with stderr/output
|
|
168
|
+
- **stopReason "error"**: LLM error propagated with error message
|
|
169
|
+
- **stopReason "aborted"**: User abort (Ctrl+C) kills subprocess, throws error
|
|
170
|
+
- **Chain mode**: Stops at first failing step, reports which step failed
|
|
171
|
+
|
|
172
|
+
## Limitations
|
|
173
|
+
|
|
174
|
+
- Output truncated to last 10 items in collapsed view (expand to see all)
|
|
175
|
+
- Parallel model-visible output is capped at 50 KB per task; full results remain in tool details
|
|
176
|
+
- Agents discovered fresh on each invocation (allows editing mid-session)
|
|
177
|
+
- Parallel mode limited to 8 tasks, 4 concurrent
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: planner
|
|
3
|
+
description: Creates implementation plans from context and requirements
|
|
4
|
+
tools: read, grep, find, ls
|
|
5
|
+
model: claude-sonnet-4-5
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are a planning specialist. You receive context (from a scout) and requirements, then produce a clear implementation plan.
|
|
9
|
+
|
|
10
|
+
You must NOT make any changes. Only read, analyze, and plan.
|
|
11
|
+
|
|
12
|
+
Input format you'll receive:
|
|
13
|
+
- Context/findings from a scout agent
|
|
14
|
+
- Original query or requirements
|
|
15
|
+
|
|
16
|
+
Output format:
|
|
17
|
+
|
|
18
|
+
## Goal
|
|
19
|
+
One sentence summary of what needs to be done.
|
|
20
|
+
|
|
21
|
+
## Plan
|
|
22
|
+
Numbered steps, each small and actionable:
|
|
23
|
+
1. Step one - specific file/function to modify
|
|
24
|
+
2. Step two - what to add/change
|
|
25
|
+
3. ...
|
|
26
|
+
|
|
27
|
+
## Files to Modify
|
|
28
|
+
- `path/to/file.ts` - what changes
|
|
29
|
+
- `path/to/other.ts` - what changes
|
|
30
|
+
|
|
31
|
+
## New Files (if any)
|
|
32
|
+
- `path/to/new.ts` - purpose
|
|
33
|
+
|
|
34
|
+
## Risks
|
|
35
|
+
Anything to watch out for.
|
|
36
|
+
|
|
37
|
+
Keep the plan concrete. The worker agent will execute it verbatim.
|