careervivid 1.10.0 → 1.11.2
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/dist/agent/QueryEngine.d.ts +64 -0
- package/dist/agent/QueryEngine.d.ts.map +1 -0
- package/dist/agent/QueryEngine.js +330 -0
- package/dist/agent/Tool.d.ts +36 -0
- package/dist/agent/Tool.d.ts.map +1 -0
- package/dist/agent/Tool.js +14 -0
- package/dist/agent/contextCompaction.d.ts +9 -0
- package/dist/agent/contextCompaction.d.ts.map +1 -0
- package/dist/agent/contextCompaction.js +68 -0
- package/dist/agent/tools/coding.d.ts +15 -0
- package/dist/agent/tools/coding.d.ts.map +1 -0
- package/dist/agent/tools/coding.js +369 -0
- package/dist/commands/agent.d.ts +3 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +218 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +12 -6
- package/dist/commands/portfolio.d.ts.map +1 -1
- package/dist/commands/portfolio.js +6 -3
- package/dist/commands/profile.d.ts.map +1 -1
- package/dist/commands/profile.js +15 -9
- package/dist/commands/publish.d.ts +18 -0
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +20 -12
- package/dist/commands/whiteboard.d.ts.map +1 -1
- package/dist/commands/whiteboard.js +8 -5
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -0
- package/dist/index.js +2 -0
- package/package.json +2 -1
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { GenerateContentResponse, Content } from '@google/genai';
|
|
2
|
+
import { Tool } from './Tool.js';
|
|
3
|
+
export declare const CODING_AGENT_SYSTEM_PROMPT: string;
|
|
4
|
+
export interface QueryEngineOptions {
|
|
5
|
+
model?: string;
|
|
6
|
+
systemInstruction?: string;
|
|
7
|
+
tools?: Tool[];
|
|
8
|
+
apiKey?: string;
|
|
9
|
+
/** GCP project ID for Vertex AI mode (uses gcloud ADC, no API key needed). */
|
|
10
|
+
project?: string;
|
|
11
|
+
/** GCP region for Vertex AI. Default: us-central1 */
|
|
12
|
+
location?: string;
|
|
13
|
+
/** Max thinking tokens for Gemini 2.5+. Set to 0 to disable. Default: 8192 */
|
|
14
|
+
thinkingBudget?: number;
|
|
15
|
+
/** Show raw thinking text in onThinking hook. Default: false */
|
|
16
|
+
includeThoughts?: boolean;
|
|
17
|
+
/** Auto-compact history when it exceeds this many turns. Default: 40 */
|
|
18
|
+
maxHistoryLength?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface IterationHook {
|
|
21
|
+
onStart?: () => void;
|
|
22
|
+
onResponse?: (response: GenerateContentResponse) => void;
|
|
23
|
+
onToolCall?: (toolName: string, args: any) => Promise<boolean | void>;
|
|
24
|
+
onToolResult?: (toolName: string, result: any) => void;
|
|
25
|
+
onError?: (error: Error) => void;
|
|
26
|
+
/** Fired for each text chunk in streaming mode */
|
|
27
|
+
onChunk?: (text: string) => void;
|
|
28
|
+
/** Fired when thinking tokens arrive (if includeThoughts is enabled) */
|
|
29
|
+
onThinking?: (thought: string) => void;
|
|
30
|
+
/** Fired when the context is being compacted */
|
|
31
|
+
onCompacting?: () => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The unified QueryEngine executing an autonomous REPL-like agentic loop
|
|
35
|
+
* using @google/genai. Supports streaming, thinking mode, automatic context
|
|
36
|
+
* compaction, and exponential-backoff retry.
|
|
37
|
+
*/
|
|
38
|
+
export declare class QueryEngine {
|
|
39
|
+
private ai;
|
|
40
|
+
private history;
|
|
41
|
+
private model;
|
|
42
|
+
private systemInstruction;
|
|
43
|
+
private tools;
|
|
44
|
+
private toolMap;
|
|
45
|
+
private thinkingBudget;
|
|
46
|
+
private includeThoughts;
|
|
47
|
+
private maxHistoryLength;
|
|
48
|
+
constructor(options?: QueryEngineOptions);
|
|
49
|
+
getHistory(): Content[];
|
|
50
|
+
setHistory(history: Content[]): void;
|
|
51
|
+
/** Add API keys / tools after construction */
|
|
52
|
+
addTools(tools: Tool[]): void;
|
|
53
|
+
private buildGenerateConfig;
|
|
54
|
+
private maybeCompact;
|
|
55
|
+
private executeToolCalls;
|
|
56
|
+
runLoop(prompt: string, hooks?: IterationHook): Promise<string>;
|
|
57
|
+
/**
|
|
58
|
+
* Like runLoop() but streams text chunks via hooks.onChunk().
|
|
59
|
+
* Tool-call iterations are still non-streaming (we need the full response
|
|
60
|
+
* to extract functionCalls reliably).
|
|
61
|
+
*/
|
|
62
|
+
runLoopStreaming(prompt: string, hooks?: IterationHook): Promise<string>;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=QueryEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QueryEngine.d.ts","sourceRoot":"","sources":["../../src/agent/QueryEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,uBAAuB,EAAE,OAAO,EAAQ,MAAM,eAAe,CAAC;AACpF,OAAO,EAAE,IAAI,EAAsB,MAAM,WAAW,CAAC;AAMrD,eAAO,MAAM,0BAA0B,QA8B/B,CAAC;AAMT,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACzD,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACtE,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACvD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,kDAAkD;IAClD,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,wEAAwE;IACxE,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAyCD;;;;GAIG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAc;IACxB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,eAAe,CAAU;IACjC,OAAO,CAAC,gBAAgB,CAAS;gBAErB,OAAO,GAAE,kBAAuB;IAyBrC,UAAU,IAAI,OAAO,EAAE;IAIvB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE;IAIpC,8CAA8C;IACvC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE;IAW7B,OAAO,CAAC,mBAAmB;YAcb,YAAY;YAOZ,gBAAgB;IA6DjB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IA8E5E;;;;OAIG;IACU,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;CA2EtF"}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { GoogleGenAI } from '@google/genai';
|
|
2
|
+
import { convertToGenAITool } from './Tool.js';
|
|
3
|
+
import { compactHistory } from './contextCompaction.js';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Elite Coding System Prompt
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
export const CODING_AGENT_SYSTEM_PROMPT = `
|
|
8
|
+
You are an expert autonomous coding agent running inside the CareerVivid CLI.
|
|
9
|
+
You have access to tools that let you read files, write files, search the codebase, and execute shell commands.
|
|
10
|
+
|
|
11
|
+
## Core Rules
|
|
12
|
+
|
|
13
|
+
1. **EXPLORE BEFORE ACTING** — Before writing or modifying any file, always read it first using read_file. Never overwrite a file blindly.
|
|
14
|
+
2. **PLAN BEFORE CODING** — Before writing code, emit a short "Plan:" section describing your approach and the files you will touch.
|
|
15
|
+
3. **MINIMAL FOOTPRINT** — Make the smallest change that correctly solves the problem. Prefer patch_file over rewriting entire files.
|
|
16
|
+
4. **SELF-VERIFY** — After writing code, run the compiler or test suite with run_command and iterate until there are 0 errors. Never claim "done" without verifying.
|
|
17
|
+
5. **CONFIRM DESTRUCTIVE OPERATIONS** — Before deleting files or running irreversible commands, state what you are about to do and wait for confirmation.
|
|
18
|
+
6. **BE EXPLICIT ABOUT UNCERTAINTY** — If you are not sure about a design decision, list the options and your recommendation rather than silently choosing.
|
|
19
|
+
|
|
20
|
+
## Workflow Pattern
|
|
21
|
+
|
|
22
|
+
For every coding task, follow this loop:
|
|
23
|
+
1. Read relevant files to understand the existing code structure.
|
|
24
|
+
2. Emit a short plan (file list, approach, risks).
|
|
25
|
+
3. Write the code using write_file or patch_file.
|
|
26
|
+
4. Verify with run_command (tsc --noEmit, npm test, etc.).
|
|
27
|
+
5. Fix any errors and loop back to step 4 until clean.
|
|
28
|
+
6. Summarize all changes made.
|
|
29
|
+
|
|
30
|
+
## Code Quality Standards
|
|
31
|
+
|
|
32
|
+
- Use TypeScript with strict types. Avoid 'any' unless unavoidable.
|
|
33
|
+
- Follow the existing code style of the file being edited.
|
|
34
|
+
- Add JSDoc comments to all public functions and interfaces.
|
|
35
|
+
- Write self-documenting code — variable names should explain intent.
|
|
36
|
+
- Keep functions short and focused on a single responsibility.
|
|
37
|
+
`.trim();
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Retry utility
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
const RETRYABLE_STATUS_CODES = new Set([429, 500, 502, 503, 504]);
|
|
42
|
+
function isRetryableError(e) {
|
|
43
|
+
// Google GenAI errors often have a status field
|
|
44
|
+
if (e?.status && RETRYABLE_STATUS_CODES.has(e.status))
|
|
45
|
+
return true;
|
|
46
|
+
if (e?.message && /rate.?limit|quota|too.?many.?requests|service.?unavailable/i.test(e.message))
|
|
47
|
+
return true;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
async function sleep(ms) {
|
|
51
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
52
|
+
}
|
|
53
|
+
async function retryWithBackoff(fn, maxRetries = 3, baseDelayMs = 1000) {
|
|
54
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
55
|
+
try {
|
|
56
|
+
return await fn();
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
if (attempt === maxRetries || !isRetryableError(e))
|
|
60
|
+
throw e;
|
|
61
|
+
const delay = baseDelayMs * Math.pow(2, attempt);
|
|
62
|
+
await sleep(delay);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// TypeScript requires this even though it's unreachable
|
|
66
|
+
throw new Error('retryWithBackoff: unreachable');
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// QueryEngine
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
/**
|
|
72
|
+
* The unified QueryEngine executing an autonomous REPL-like agentic loop
|
|
73
|
+
* using @google/genai. Supports streaming, thinking mode, automatic context
|
|
74
|
+
* compaction, and exponential-backoff retry.
|
|
75
|
+
*/
|
|
76
|
+
export class QueryEngine {
|
|
77
|
+
ai;
|
|
78
|
+
history = [];
|
|
79
|
+
model;
|
|
80
|
+
systemInstruction;
|
|
81
|
+
tools;
|
|
82
|
+
toolMap;
|
|
83
|
+
thinkingBudget;
|
|
84
|
+
includeThoughts;
|
|
85
|
+
maxHistoryLength;
|
|
86
|
+
constructor(options = {}) {
|
|
87
|
+
const apiKey = options.apiKey;
|
|
88
|
+
const project = options.project ?? process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GCLOUD_PROJECT;
|
|
89
|
+
const location = options.location ?? process.env.GOOGLE_CLOUD_LOCATION ?? 'us-central1';
|
|
90
|
+
const useVertexAI = !apiKey && !!project;
|
|
91
|
+
if (useVertexAI) {
|
|
92
|
+
// Vertex AI mode — uses gcloud Application Default Credentials
|
|
93
|
+
this.ai = new GoogleGenAI({ vertexai: true, project, location });
|
|
94
|
+
// Vertex AI uses a different model namespace
|
|
95
|
+
this.model = options.model || 'gemini-2.5-flash-preview-04-17';
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Gemini API mode — requires an API key
|
|
99
|
+
this.ai = new GoogleGenAI({ apiKey: apiKey || '' });
|
|
100
|
+
this.model = options.model || 'gemini-2.5-flash';
|
|
101
|
+
}
|
|
102
|
+
this.systemInstruction = options.systemInstruction || CODING_AGENT_SYSTEM_PROMPT;
|
|
103
|
+
this.tools = options.tools || [];
|
|
104
|
+
this.toolMap = new Map(this.tools.map(t => [t.name, t]));
|
|
105
|
+
this.thinkingBudget = options.thinkingBudget ?? 8192;
|
|
106
|
+
this.includeThoughts = options.includeThoughts ?? false;
|
|
107
|
+
this.maxHistoryLength = options.maxHistoryLength ?? 40;
|
|
108
|
+
}
|
|
109
|
+
getHistory() {
|
|
110
|
+
return this.history;
|
|
111
|
+
}
|
|
112
|
+
setHistory(history) {
|
|
113
|
+
this.history = history;
|
|
114
|
+
}
|
|
115
|
+
/** Add API keys / tools after construction */
|
|
116
|
+
addTools(tools) {
|
|
117
|
+
for (const t of tools) {
|
|
118
|
+
this.tools.push(t);
|
|
119
|
+
this.toolMap.set(t.name, t);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// -------------------------------------------------------------------------
|
|
123
|
+
// Private helpers
|
|
124
|
+
// -------------------------------------------------------------------------
|
|
125
|
+
buildGenerateConfig(genAITools) {
|
|
126
|
+
const config = {
|
|
127
|
+
systemInstruction: this.systemInstruction,
|
|
128
|
+
};
|
|
129
|
+
if (genAITools.length > 0)
|
|
130
|
+
config.tools = genAITools;
|
|
131
|
+
if (this.thinkingBudget > 0) {
|
|
132
|
+
config.thinkingConfig = {
|
|
133
|
+
thinkingBudget: this.thinkingBudget,
|
|
134
|
+
includeThoughts: this.includeThoughts,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return config;
|
|
138
|
+
}
|
|
139
|
+
async maybeCompact(hooks) {
|
|
140
|
+
if (this.history.length > this.maxHistoryLength) {
|
|
141
|
+
if (hooks?.onCompacting)
|
|
142
|
+
hooks.onCompacting();
|
|
143
|
+
this.history = await compactHistory(this.ai, this.history, this.model);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async executeToolCalls(functionCalls, hooks) {
|
|
147
|
+
const functionResponses = [];
|
|
148
|
+
for (const call of functionCalls) {
|
|
149
|
+
const toolName = call.name || '';
|
|
150
|
+
const args = call.args || {};
|
|
151
|
+
const tool = this.toolMap.get(toolName);
|
|
152
|
+
if (!tool) {
|
|
153
|
+
functionResponses.push({
|
|
154
|
+
functionResponse: {
|
|
155
|
+
name: toolName,
|
|
156
|
+
response: { error: `Tool "${toolName}" not found. Available tools: ${[...this.toolMap.keys()].join(', ')}` },
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
// Confirmation gate
|
|
162
|
+
if (hooks?.onToolCall) {
|
|
163
|
+
const allow = await hooks.onToolCall(toolName, args);
|
|
164
|
+
if (allow === false) {
|
|
165
|
+
functionResponses.push({
|
|
166
|
+
functionResponse: {
|
|
167
|
+
name: toolName,
|
|
168
|
+
response: { error: 'User denied tool execution.' },
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
const result = await tool.execute(args);
|
|
176
|
+
if (hooks?.onToolResult)
|
|
177
|
+
hooks.onToolResult(toolName, result);
|
|
178
|
+
functionResponses.push({
|
|
179
|
+
functionResponse: {
|
|
180
|
+
name: toolName,
|
|
181
|
+
response: { result },
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
functionResponses.push({
|
|
187
|
+
functionResponse: {
|
|
188
|
+
name: toolName,
|
|
189
|
+
response: { error: String(err.message || err) },
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return functionResponses;
|
|
195
|
+
}
|
|
196
|
+
// -------------------------------------------------------------------------
|
|
197
|
+
// Non-streaming loop
|
|
198
|
+
// -------------------------------------------------------------------------
|
|
199
|
+
async runLoop(prompt, hooks) {
|
|
200
|
+
this.history.push({ role: 'user', parts: [{ text: prompt }] });
|
|
201
|
+
const genAITools = this.tools.map(convertToGenAITool);
|
|
202
|
+
const config = this.buildGenerateConfig(genAITools);
|
|
203
|
+
let maxIterations = 30;
|
|
204
|
+
let iterations = 0;
|
|
205
|
+
let finalAnswer = '';
|
|
206
|
+
while (iterations < maxIterations) {
|
|
207
|
+
iterations++;
|
|
208
|
+
if (hooks?.onStart)
|
|
209
|
+
hooks.onStart();
|
|
210
|
+
// Auto-compact before hitting the API
|
|
211
|
+
await this.maybeCompact(hooks);
|
|
212
|
+
try {
|
|
213
|
+
const response = await retryWithBackoff(() => this.ai.models.generateContent({
|
|
214
|
+
model: this.model,
|
|
215
|
+
contents: this.history,
|
|
216
|
+
config: config,
|
|
217
|
+
}));
|
|
218
|
+
if (hooks?.onResponse)
|
|
219
|
+
hooks.onResponse(response);
|
|
220
|
+
// Capture thinking text if included
|
|
221
|
+
if (this.includeThoughts && hooks?.onThinking) {
|
|
222
|
+
const thoughtParts = response.candidates?.[0]?.content?.parts?.filter((p) => p.thought);
|
|
223
|
+
for (const part of thoughtParts || []) {
|
|
224
|
+
if (part.text)
|
|
225
|
+
hooks.onThinking(part.text);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Separate tool calls from text parts
|
|
229
|
+
const functionCalls = response.functionCalls;
|
|
230
|
+
const hasToolCalls = functionCalls && functionCalls.length > 0;
|
|
231
|
+
// Add model response to history
|
|
232
|
+
this.history.push({
|
|
233
|
+
role: 'model',
|
|
234
|
+
parts: hasToolCalls
|
|
235
|
+
? functionCalls.map(fc => ({
|
|
236
|
+
functionCall: { name: fc.name, args: fc.args },
|
|
237
|
+
}))
|
|
238
|
+
: [{ text: response.text || '' }],
|
|
239
|
+
});
|
|
240
|
+
if (!hasToolCalls) {
|
|
241
|
+
finalAnswer = response.text || '';
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
const functionResponses = await this.executeToolCalls(functionCalls, hooks);
|
|
245
|
+
this.history.push({ role: 'user', parts: functionResponses });
|
|
246
|
+
}
|
|
247
|
+
catch (e) {
|
|
248
|
+
if (hooks?.onError)
|
|
249
|
+
hooks.onError(e);
|
|
250
|
+
finalAnswer = `Generation Error: ${e.message}`;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (iterations >= maxIterations) {
|
|
255
|
+
finalAnswer = finalAnswer || 'Max iterations (30) exceeded without a final answer.';
|
|
256
|
+
}
|
|
257
|
+
return finalAnswer;
|
|
258
|
+
}
|
|
259
|
+
// -------------------------------------------------------------------------
|
|
260
|
+
// Streaming loop
|
|
261
|
+
// -------------------------------------------------------------------------
|
|
262
|
+
/**
|
|
263
|
+
* Like runLoop() but streams text chunks via hooks.onChunk().
|
|
264
|
+
* Tool-call iterations are still non-streaming (we need the full response
|
|
265
|
+
* to extract functionCalls reliably).
|
|
266
|
+
*/
|
|
267
|
+
async runLoopStreaming(prompt, hooks) {
|
|
268
|
+
this.history.push({ role: 'user', parts: [{ text: prompt }] });
|
|
269
|
+
const genAITools = this.tools.map(convertToGenAITool);
|
|
270
|
+
const config = this.buildGenerateConfig(genAITools);
|
|
271
|
+
let maxIterations = 30;
|
|
272
|
+
let iterations = 0;
|
|
273
|
+
let finalAnswer = '';
|
|
274
|
+
while (iterations < maxIterations) {
|
|
275
|
+
iterations++;
|
|
276
|
+
if (hooks?.onStart)
|
|
277
|
+
hooks.onStart();
|
|
278
|
+
await this.maybeCompact(hooks);
|
|
279
|
+
try {
|
|
280
|
+
// Use streaming for final text responses; non-streaming for tool call turns
|
|
281
|
+
const streamResponse = await retryWithBackoff(() => this.ai.models.generateContentStream({
|
|
282
|
+
model: this.model,
|
|
283
|
+
contents: this.history,
|
|
284
|
+
config: config,
|
|
285
|
+
}));
|
|
286
|
+
let accumulatedText = '';
|
|
287
|
+
let accumulatedFunctionCalls = [];
|
|
288
|
+
for await (const chunk of streamResponse) {
|
|
289
|
+
// Text chunk
|
|
290
|
+
const chunkText = chunk.text;
|
|
291
|
+
if (chunkText) {
|
|
292
|
+
accumulatedText += chunkText;
|
|
293
|
+
if (hooks?.onChunk)
|
|
294
|
+
hooks.onChunk(chunkText);
|
|
295
|
+
}
|
|
296
|
+
// Accumulate function calls
|
|
297
|
+
if (chunk.functionCalls) {
|
|
298
|
+
accumulatedFunctionCalls.push(...chunk.functionCalls);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const hasToolCalls = accumulatedFunctionCalls.length > 0;
|
|
302
|
+
// Add model turn to history
|
|
303
|
+
this.history.push({
|
|
304
|
+
role: 'model',
|
|
305
|
+
parts: hasToolCalls
|
|
306
|
+
? accumulatedFunctionCalls.map(fc => ({
|
|
307
|
+
functionCall: { name: fc.name, args: fc.args },
|
|
308
|
+
}))
|
|
309
|
+
: [{ text: accumulatedText }],
|
|
310
|
+
});
|
|
311
|
+
if (!hasToolCalls) {
|
|
312
|
+
finalAnswer = accumulatedText;
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
const functionResponses = await this.executeToolCalls(accumulatedFunctionCalls, hooks);
|
|
316
|
+
this.history.push({ role: 'user', parts: functionResponses });
|
|
317
|
+
}
|
|
318
|
+
catch (e) {
|
|
319
|
+
if (hooks?.onError)
|
|
320
|
+
hooks.onError(e);
|
|
321
|
+
finalAnswer = `Generation Error: ${e.message}`;
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (iterations >= maxIterations) {
|
|
326
|
+
finalAnswer = finalAnswer || 'Max iterations (30) exceeded without a final answer.';
|
|
327
|
+
}
|
|
328
|
+
return finalAnswer;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Type } from '@google/genai';
|
|
2
|
+
/**
|
|
3
|
+
* Interface defining the parameters schema for a tool.
|
|
4
|
+
* We use a subset of the Open API/JSON Schema specifically used by Google GenAI.
|
|
5
|
+
*/
|
|
6
|
+
export interface ToolParameters {
|
|
7
|
+
type: Type | string;
|
|
8
|
+
properties?: Record<string, any>;
|
|
9
|
+
required?: string[];
|
|
10
|
+
description?: string;
|
|
11
|
+
enum?: string[];
|
|
12
|
+
items?: ToolParameters;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* An abstraction of the Claude Code Tool system, adapted for Generation AI.
|
|
16
|
+
*/
|
|
17
|
+
export interface Tool<P = any, R = any> {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
parameters: ToolParameters;
|
|
21
|
+
/**
|
|
22
|
+
* Whether this tool should require user confirmation in the CLI / UI before running.
|
|
23
|
+
*/
|
|
24
|
+
requiresConfirmation?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Evaluated when the LLM triggers the tool.
|
|
27
|
+
* @param params Validated arguments from the LLM.
|
|
28
|
+
* @returns Resolves with JSON-serializable outputs or string.
|
|
29
|
+
*/
|
|
30
|
+
execute: (params: P) => Promise<R>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Converts a Tool definition into a format accepted by @google/genai tool arrays.
|
|
34
|
+
*/
|
|
35
|
+
export declare function convertToGenAITool(tool: Tool): any;
|
|
36
|
+
//# sourceMappingURL=Tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tool.d.ts","sourceRoot":"","sources":["../../src/agent/Tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,IAAI,GAAG,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,cAAc,CAAC;IAC3B;;OAEG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,GAAG,CAUlD"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a Tool definition into a format accepted by @google/genai tool arrays.
|
|
3
|
+
*/
|
|
4
|
+
export function convertToGenAITool(tool) {
|
|
5
|
+
return {
|
|
6
|
+
functionDeclarations: [
|
|
7
|
+
{
|
|
8
|
+
name: tool.name,
|
|
9
|
+
description: tool.description,
|
|
10
|
+
parameters: tool.parameters,
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Content, GoogleGenAI } from '@google/genai';
|
|
2
|
+
/**
|
|
3
|
+
* Coding-session-aware context compaction.
|
|
4
|
+
* Summarizes the old portion of the conversation history while preserving
|
|
5
|
+
* key artifacts: file paths touched, errors encountered, fixes applied,
|
|
6
|
+
* and design decisions made. Mimics Claude Code's context sliding-window.
|
|
7
|
+
*/
|
|
8
|
+
export declare function compactHistory(ai: GoogleGenAI, history: Content[], model?: string, preserveCount?: number): Promise<Content[]>;
|
|
9
|
+
//# sourceMappingURL=contextCompaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contextCompaction.d.ts","sourceRoot":"","sources":["../../src/agent/contextCompaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAErD;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,EAAE,EAAE,WAAW,EACf,OAAO,EAAE,OAAO,EAAE,EAClB,KAAK,GAAE,MAA2B,EAClC,aAAa,GAAE,MAAU,GACxB,OAAO,CAAC,OAAO,EAAE,CAAC,CAoEpB"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coding-session-aware context compaction.
|
|
3
|
+
* Summarizes the old portion of the conversation history while preserving
|
|
4
|
+
* key artifacts: file paths touched, errors encountered, fixes applied,
|
|
5
|
+
* and design decisions made. Mimics Claude Code's context sliding-window.
|
|
6
|
+
*/
|
|
7
|
+
export async function compactHistory(ai, history, model = 'gemini-2.5-flash', preserveCount = 6) {
|
|
8
|
+
if (history.length <= preserveCount + 2) {
|
|
9
|
+
return history;
|
|
10
|
+
}
|
|
11
|
+
// Split history into old vs new
|
|
12
|
+
// Preserve the first system or initial context if it exists
|
|
13
|
+
const isFirstSystem = history[0].role === 'system';
|
|
14
|
+
const startIdx = isFirstSystem ? 1 : 0;
|
|
15
|
+
const toCompact = history.slice(startIdx, history.length - preserveCount);
|
|
16
|
+
const preserved = history.slice(history.length - preserveCount);
|
|
17
|
+
// Coding-aware summarization prompt
|
|
18
|
+
const summaryPrompt = {
|
|
19
|
+
role: 'user',
|
|
20
|
+
parts: [{
|
|
21
|
+
text: [
|
|
22
|
+
'You are summarizing a coding agent session. Produce a concise but accurate ledger that preserves:',
|
|
23
|
+
'1. Every file path that was read, created, or modified (list each with a one-line summary of changes).',
|
|
24
|
+
'2. Any TypeScript/compiler errors encountered and their resolutions.',
|
|
25
|
+
'3. Key design decisions or constraints established during the session.',
|
|
26
|
+
'4. The current goal/task the agent was working on at the end of this segment.',
|
|
27
|
+
'',
|
|
28
|
+
'Format:',
|
|
29
|
+
'## Files Touched',
|
|
30
|
+
'- path/to/file.ts — [what changed]',
|
|
31
|
+
'',
|
|
32
|
+
'## Errors & Fixes',
|
|
33
|
+
'- [error] → [fix applied]',
|
|
34
|
+
'',
|
|
35
|
+
'## Decisions & Constraints',
|
|
36
|
+
'- [decision]',
|
|
37
|
+
'',
|
|
38
|
+
'## Current Task',
|
|
39
|
+
'[one sentence]',
|
|
40
|
+
'',
|
|
41
|
+
'---',
|
|
42
|
+
'Conversation to summarize:',
|
|
43
|
+
JSON.stringify(toCompact, null, 2),
|
|
44
|
+
].join('\n')
|
|
45
|
+
}]
|
|
46
|
+
};
|
|
47
|
+
try {
|
|
48
|
+
const response = await ai.models.generateContent({
|
|
49
|
+
model: model,
|
|
50
|
+
contents: [summaryPrompt],
|
|
51
|
+
});
|
|
52
|
+
const summaryText = response.text || 'No summary generated.';
|
|
53
|
+
const compactedNode = {
|
|
54
|
+
role: 'user',
|
|
55
|
+
parts: [{
|
|
56
|
+
text: `[SYSTEM] The following is a compacted summary of previous interactions:\n${summaryText}`
|
|
57
|
+
}]
|
|
58
|
+
};
|
|
59
|
+
const newHistory = isFirstSystem ? [history[0]] : [];
|
|
60
|
+
newHistory.push(compactedNode);
|
|
61
|
+
newHistory.push(...preserved);
|
|
62
|
+
return newHistory;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.warn('Compaction failed. Returning original history.', error);
|
|
66
|
+
return history;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Tool } from '../Tool.js';
|
|
2
|
+
declare function isSafeCommand(cmd: string): boolean;
|
|
3
|
+
export declare const readFileTool: Tool;
|
|
4
|
+
export declare const writeFileTool: Tool;
|
|
5
|
+
export declare const patchFileTool: Tool;
|
|
6
|
+
export declare const listDirectoryTool: Tool;
|
|
7
|
+
export declare const searchFilesTool: Tool;
|
|
8
|
+
export declare const runCommandTool: Tool;
|
|
9
|
+
/** Override requiresConfirmation for safe commands at runtime */
|
|
10
|
+
export declare function createSmartRunCommandTool(): Tool;
|
|
11
|
+
export declare const getFileTreeTool: Tool;
|
|
12
|
+
/** All tools suitable for coding-mode agent sessions */
|
|
13
|
+
export declare const ALL_CODING_TOOLS: Tool[];
|
|
14
|
+
export { isSafeCommand };
|
|
15
|
+
//# sourceMappingURL=coding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coding.d.ts","sourceRoot":"","sources":["../../../src/agent/tools/coding.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAyBlC,iBAAS,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAG3C;AAMD,eAAO,MAAM,YAAY,EAAE,IAqC1B,CAAC;AAMF,eAAO,MAAM,aAAa,EAAE,IA0B3B,CAAC;AAMF,eAAO,MAAM,aAAa,EAAE,IA+C3B,CAAC;AAMF,eAAO,MAAM,iBAAiB,EAAE,IA8C/B,CAAC;AAMF,eAAO,MAAM,eAAe,EAAE,IA8C7B,CAAC;AAMF,eAAO,MAAM,cAAc,EAAE,IAmD5B,CAAC;AAEF,iEAAiE;AACjE,wBAAgB,yBAAyB,IAAI,IAAI,CAQhD;AAMD,eAAO,MAAM,eAAe,EAAE,IAkD7B,CAAC;AAMF,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,EAAE,IAAI,EAQlC,CAAC;AAEF,OAAO,EAAE,aAAa,EAAE,CAAC"}
|