Skip to content
120 changes: 108 additions & 12 deletions extensions/cli/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions extensions/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
},
"homepage": "https://continue.dev",
"dependencies": {
"@raindrop-ai/ai-sdk": "^0.0.15",
"ai": "^6.0.86",
"@sentry/profiling-node": "^9.47.1",
"fdir": "^6.4.2",
"find-up": "^8.0.0",
Expand Down
4 changes: 4 additions & 0 deletions extensions/cli/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,10 @@ export async function initializeServices(initOptions: ServiceInitOptions = {}) {
[], // No dependencies
);

// Initialize Raindrop observability before services create LLM API instances
const raindropService = await import("../telemetry/raindropService.js");
await raindropService.initialize();

// Eagerly initialize all services to ensure they're ready when needed
// This avoids race conditions and "service not ready" errors
await serviceContainer.initializeAll();
Expand Down
4 changes: 2 additions & 2 deletions extensions/cli/src/smoke-api/headless-all-tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
createSmokeContext,
cleanupSmokeContext,
writeAnthropicConfig,
runHeadless,
runHeadlessWithRetry,
type SmokeTestContext,
} from "./smoke-api-helpers.js";

Expand Down Expand Up @@ -62,7 +62,7 @@ describe.skipIf(!ANTHROPIC_API_KEY)(
After all tools complete, say exactly "ALL_TOOLS_DONE" as your final message.`;

it("should exercise 10 built-in tools and complete successfully", async () => {
const result = await runHeadless(
const result = await runHeadlessWithRetry(
ctx,
["-p", "--auto", "--config", ctx.configPath, PROMPT],
{ timeout: 120000 },
Expand Down
4 changes: 2 additions & 2 deletions extensions/cli/src/smoke-api/headless-anthropic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
createSmokeContext,
cleanupSmokeContext,
writeAnthropicConfig,
runHeadless,
runHeadlessWithRetry,
type SmokeTestContext,
} from "./smoke-api-helpers.js";

Expand All @@ -25,7 +25,7 @@ describe.skipIf(!ANTHROPIC_API_KEY)(
});

it("should complete a round-trip and return a response", async () => {
const result = await runHeadless(ctx, [
const result = await runHeadlessWithRetry(ctx, [
"-p",
"--config",
ctx.configPath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
createSmokeContext,
cleanupSmokeContext,
writeContinueProxyConfig,
runHeadless,
runHeadlessWithRetry,
type SmokeTestContext,
} from "./smoke-api-helpers.js";

Expand All @@ -29,7 +29,7 @@ describe.skipIf(!CONTINUE_API_KEY || !SMOKE_PROXY_MODEL)(
});

it("should complete a round-trip and return a response", async () => {
const result = await runHeadless(ctx, [
const result = await runHeadlessWithRetry(ctx, [
"-p",
"--config",
ctx.configPath,
Expand Down
38 changes: 8 additions & 30 deletions extensions/cli/src/smoke-api/serve-anthropic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
writeAnthropicConfig,
spawnServe,
waitForPattern,
pollUntilIdle,
sendMessageAndWait,
getLastAssistantContent,
shutdownServe,
type SmokeTestContext,
} from "./smoke-api-helpers.js";
Expand Down Expand Up @@ -45,37 +46,14 @@ describe.skipIf(!ANTHROPIC_API_KEY)(
// Wait for the server to start
await waitForPattern(proc, "Server started", 30000);

// Send a message
const msgRes = await fetch(`${baseUrl}/message`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: "Reply with exactly the word 'hello' and nothing else.",
}),
});
expect(msgRes.ok).toBe(true);

// Poll until the agent finishes processing
const state = await pollUntilIdle(baseUrl, 60000);

// State shape: { session: { history: ChatHistoryItem[] }, ... }
// Each ChatHistoryItem has { message: { role, content }, ... }
const history: any[] = state.session?.history ?? [];
const assistantItems = history.filter(
(item: any) => item.message?.role === "assistant",
// Send a message (retries with backoff on rate-limit errors)
const state = await sendMessageAndWait(
baseUrl,
"Reply with exactly the word 'hello' and nothing else.",
);
expect(assistantItems.length).toBeGreaterThan(0);

const lastMsg = assistantItems[assistantItems.length - 1].message;
const content =
typeof lastMsg.content === "string"
? lastMsg.content
: lastMsg.content
?.filter((p: any) => p.type === "text")
.map((p: any) => p.text)
.join("");

expect(content?.toLowerCase()).toContain("hello");
const content = getLastAssistantContent(state);
expect(content).toContain("hello");

// Graceful exit
const exitRes = await fetch(`${baseUrl}/exit`, { method: "POST" });
Expand Down
Loading
Loading