Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
},
"devDependencies": {
"@caido/sdk-backend": "0.55.3",
"@caido/sdk-frontend": "0.55.3",
"@caido/sdk-frontend": "0.56.3-beta.0",
"@codemirror/view": "6.39.16",
"vue-tsc": "3.2.5"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/agent/collection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function showLaunchDialog(sdk: FrontendSDK, sessionId: string, preSelectedSkillI

dialog = sdk.window.showDialog(
{
component: LaunchDialog,
component: LaunchDialog as never,
props: {
onConfirm: () => handleConfirm,
onCancel: () => handleCancel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ export const FindingsCreate = tool({
return ToolResult.err("No active entry found in replay session");
}

const requestId = activeEntry.request?.id;
const requestId =
activeEntry.__typename === "ReplayEntryWs"
? activeEntry.http.request?.id
: activeEntry.request?.id;
if (requestId === undefined || requestId === null || requestId === "") {
return ToolResult.err("Request ID not found in active entry");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Returns request metadata (id, host, port, path, query, method, isTls, createdAt,
const graphqlResult = await safeGraphQL(
() =>
context.sdk.graphql.requestsByOffset({
filter,
filter: { code: filter },
limit,
offset,
order: {
Expand Down
30 changes: 12 additions & 18 deletions packages/frontend/src/agent/tools/replay/ReplayEntryNavigate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { z } from "zod";

import type { AgentContext } from "@/agent/context";
import { type ToolDisplay, ToolResult, type ToolResult as ToolResultType } from "@/agent/types";
import { isPresent } from "@/utils/optional";
import { getReplayEntryRequest } from "@/utils/caido";

const inputSchema = z.object({
entryId: z
Expand All @@ -16,6 +16,7 @@ const inputSchema = z.object({
const valueSchema = z.object({
entryId: z.string(),
requestId: z.string().optional(),
requestRaw: z.string().optional(),
});

const outputSchema = ToolResult.schema(valueSchema);
Expand Down Expand Up @@ -46,33 +47,26 @@ export const ReplayEntryNavigate = tool({
const context = experimental_context as AgentContext;
const sdk = context.sdk;

const entry = sdk.replay.getEntry(entryId);
if (!isPresent(entry)) {
return ToolResult.err("Entry not found");
const entryResult = await getReplayEntryRequest(sdk, entryId, context.sessionId);
if (entryResult.kind === "Error") {
return ToolResult.err("Entry not found", entryResult.error);
}

if (entry.sessionId !== context.sessionId) {
const { entry, request } = entryResult.value;

if (entry.session.id !== context.sessionId) {
return ToolResult.err("Entry does not belong to the current session");
}

await sdk.replay.showEntry(context.sessionId, entryId, {
overwriteDraft: true,
});
await sdk.replay.showEntry(context.sessionId, entryId);

const entryResult = await sdk.graphql.replayEntry({
id: entryId,
});
const replayEntry = entryResult.replayEntry;

if (isPresent(replayEntry)) {
context.setHttpRequest(replayEntry.raw);
}
context.setHttpRequest(entry.raw);

return ToolResult.ok({
message: `Navigated to entry ${entryId}`,
entryId,
requestId: entry.requestId,
requestRaw: replayEntry?.raw,
requestId: request?.id,
requestRaw: entry.raw,
});
} catch (error) {
return ToolResult.err("Failed to navigate to entry", (error as Error).message);
Expand Down
100 changes: 87 additions & 13 deletions packages/frontend/src/agent/tools/request/RequestSend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { z } from "zod";

import type { AgentContext } from "@/agent/context";
import { type ToolDisplay, ToolResult, type ToolResult as ToolResultType } from "@/agent/types";
import { getReplaySession } from "@/utils/caido";
import { getReplaySession, usesReplayEntryInterface, writeToRequestEditor } from "@/utils/caido";
import { formatStringWithSuffix } from "@/utils/text";

const inputSchema = z.object({});
Expand All @@ -21,6 +21,34 @@ type RequestSendInput = z.infer<typeof inputSchema>;
type RequestSendValue = z.infer<typeof valueSchema>;
type RequestSendOutput = ToolResultType<RequestSendValue>;

type LegacySendRequestOptions = {
background?: boolean;
connectionInfo: {
host: string;
isTLS: boolean;
port: number;
SNI?: string;
};
raw: string;
};

type LegacySendRequest = (sessionId: string, options: LegacySendRequestOptions) => Promise<void>;

const getActiveEntryResponseId = async (
sdk: AgentContext["sdk"],
sessionId: string
): Promise<string | undefined> => {
const result = await sdk.graphql.replaySessionEntries({ id: sessionId });
const activeEntry = result.replaySession?.activeEntry;

return activeEntry?.__typename === "ReplayEntryWs"
? activeEntry.http.request?.response?.id
: activeEntry?.request?.response?.id;
};

const sleep = (duration: number): Promise<void> =>
new Promise((resolve) => setTimeout(resolve, duration));

export const formatRequestSendModelOutput = (value: {
message: string;
rawResponse: string;
Expand Down Expand Up @@ -113,17 +141,47 @@ export const RequestSend = tool({

const replaySession = replaySessionResult.value;

await sdk.replay.sendRequest(replaySession.id, {
connectionInfo: {
host: replaySession.request.host,
isTLS: replaySession.request.isTLS,
port: replaySession.request.port,
},
raw: context.httpRequest,
background: true,
});
const previousResponseId = await getActiveEntryResponseId(sdk, replaySession.id);

let responseId: string | undefined = undefined;

const captureResponseId = (nextResponseId: string | undefined): boolean => {
if (nextResponseId !== undefined && nextResponseId !== previousResponseId) {
responseId = nextResponseId;
return true;
}

return false;
};

try {
if (usesReplayEntryInterface(sdk)) {
writeToRequestEditor(context.httpRequest);
await sdk.replay.sendRequest(replaySession.id, {
background: false,
});
} else {
if (replaySession.request.host === "" || replaySession.request.port === 0) {
return ToolResult.err("No active replay entry to send");
}

const sendRequest = sdk.replay.sendRequest as unknown as LegacySendRequest;
await sendRequest(replaySession.id, {
background: false,
connectionInfo: {
host: replaySession.request.host,
isTLS: replaySession.request.isTLS,
port: replaySession.request.port,
SNI: replaySession.request.SNI || undefined,
},
raw: context.httpRequest,
});
}
} catch (error) {
const detail = error instanceof Error ? error.message : "Unknown error occurred";
return ToolResult.err("Failed to send request", detail);
}

const iterator = sdk.graphql.updatedReplaySession({});

const timeout = new Promise<never>((_, reject) => {
Expand All @@ -144,15 +202,31 @@ export const RequestSend = tool({
const responsePromise = (async () => {
for await (const event of iterator) {
if (event.updatedReplaySession.sessionEdge.node.id === replaySession.id) {
responseId =
event.updatedReplaySession.sessionEdge.node.activeEntry?.request?.response?.id;
const activeEntry = event.updatedReplaySession.sessionEdge.node.activeEntry;
const nextResponseId =
activeEntry?.__typename === "ReplayEntryWs"
? activeEntry.http.request?.response?.id
: activeEntry?.request?.response?.id;

if (captureResponseId(nextResponseId ?? undefined)) {
break;
}
}
}
})();

const pollResponsePromise = (async () => {
while (responseId === undefined) {
if (captureResponseId(await getActiveEntryResponseId(sdk, replaySession.id))) {
break;
}

await sleep(250);
}
})();

try {
await Promise.race([responsePromise, timeout, abortPromise]);
await Promise.race([responsePromise, pollResponsePromise, timeout, abortPromise]);
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
throw error;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export const automateSessionCreateTool = tool({
}

const graphPayloads = attachDefaultPreprocessors((payloads ?? []).map(toGraphQLPayload));
const { extractors, ...settings } = session.settings;

await sdk.graphql.updateAutomateSession({
id: session.id,
Expand All @@ -174,9 +175,16 @@ export const automateSessionCreateTool = tool({
},
raw: session.raw,
settings: {
...session.settings,
...settings,
strategy: strategy ?? "ALL",
concurrency: concurrency ?? { delay: 0, workers: 10 },
extractors: extractors.map(({ body, regex, workflowId }) => ({
automateExtractorRegex: {
body,
regex,
workflowId,
},
})),
payloads: graphPayloads,
placeholders,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export const historyReadTool = tool({

try {
const result = await sdk.graphql.interceptEntriesByOffset({
filter,
filter: filter !== undefined ? { code: filter } : undefined,
limit,
offset,
scopeId: effectiveScopeId,
Expand Down
37 changes: 26 additions & 11 deletions packages/frontend/src/float/actions/replay/ReplaySessionCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { z } from "zod";

import { normalizeRawHttpRequest } from "@/agent/utils/http";
import { ActionResult, type FloatToolContext } from "@/float/types";
import { type FrontendSDK } from "@/types";
import { usesReplayEntryInterface } from "@/utils/caido";

const inputSchema = z.object({
rawRequest: z.string().describe("Raw HTTP request source (non-empty)"),
Expand All @@ -21,19 +23,32 @@ export const replaySessionCreateTool = tool({
outputSchema: ActionResult.schema,
execute: async ({ rawRequest, host, port, isTls, sessionName }, { experimental_context }) => {
const { sdk } = experimental_context as FloatToolContext;
const result = await sdk.graphql.createReplaySession({
input: {
requestSource: {
raw: {
raw: normalizeRawHttpRequest(rawRequest),
connectionInfo: {
host,
port,
isTLS: isTls,
},
},
type CreateReplaySessionVariables = Parameters<
FrontendSDK["graphql"]["createReplaySession"]
>[0];
const requestSource = {
raw: {
raw: normalizeRawHttpRequest(rawRequest),
connectionInfo: {
host,
port,
isTLS: isTls,
},
},
};
const input = (
usesReplayEntryInterface(sdk)
? {
kind: "HTTP",
requestSource,
}
: {
requestSource,
}
) as CreateReplaySessionVariables["input"];

const result = await sdk.graphql.createReplaySession({
input,
});

const sessionId = result.createReplaySession.session?.id;
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/float/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ const showConfirmationDialog = (
) => {
const dialog = sdk.window.showDialog(
{
component: ConfirmationDialog,
component: ConfirmationDialog as never,
props: {
fileName,
content,
Expand Down
6 changes: 3 additions & 3 deletions packages/frontend/src/renaming/ai.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { type ReplayEntryQuery } from "@caido/sdk-frontend/src/types/__generated__/graphql-sdk";
import { generateText, Output } from "ai";
import { Result } from "shared";
import { z } from "zod";
Expand All @@ -7,6 +6,7 @@ import { useModelsStore } from "@/stores/models";
import { useSettingsStore } from "@/stores/settings";
import { type FrontendSDK } from "@/types";
import { createModel, resolveModel } from "@/utils";
import { type ReplayEntryWithRequest } from "@/utils/caido";

const outputSchema = z.object({
name: z.string(),
Expand Down Expand Up @@ -66,7 +66,7 @@ OUTPUT:

export async function generateName(
sdk: FrontendSDK,
entry: ReplayEntryQuery
entry: ReplayEntryWithRequest
): Promise<Result<string>> {
try {
const settingsStore = useSettingsStore();
Expand All @@ -88,7 +88,7 @@ export async function generateName(

const prompt = `
<entry>
${JSON.stringify(entry)}
${JSON.stringify(entry.entry)}
</entry>

<instructions>
Expand Down
11 changes: 10 additions & 1 deletion packages/frontend/src/renaming/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,22 @@ import {
import { generateName } from "@/renaming/ai";
import { useSettingsStore } from "@/stores/settings";
import { type FrontendSDK } from "@/types";
import { getReplayEntryRequest } from "@/utils/caido";
import { isPresent } from "@/utils/optional";

export const setupRenaming = (sdk: FrontendSDK) => {
const settingsStore = useSettingsStore();

const renameSession = async (entryId: string, sessionId: string) => {
const nameResult = await generateName(sdk, await sdk.graphql.replayEntry({ id: entryId }));
const entryResult = await getReplayEntryRequest(sdk, entryId, sessionId);
if (entryResult.kind === "Error") {
sdk.window.showToast(`[Shift] Failed while fetching replay entry: ${entryResult.error}`, {
variant: "error",
});
return;
}

const nameResult = await generateName(sdk, entryResult.value);
if (nameResult.kind === "Error") {
sdk.window.showToast(`[Shift] Failed while renaming session: ${nameResult.error}`, {
variant: "error",
Expand Down
Loading
Loading