


























@@ -330,6 +330,49 @@ function lastMockCallArg<T>(
330330return mockCallArg<T>(mock, -1, argIndex, _type);
331331}
332332333+function firstResultText(result: { content?: readonly unknown[] } | undefined): string {
334+const block = result?.content?.[0] as { type?: unknown; text?: unknown } | undefined;
335+expect(block?.type).toBe("text");
336+expect(typeof block?.text).toBe("string");
337+return block?.text as string;
338+}
339+340+function externalContentDetails(
341+result: { details?: unknown } | undefined,
342+kind: string,
343+): {
344+externalContent?: { untrusted?: unknown; source?: unknown; kind?: unknown };
345+format?: unknown;
346+messageCount?: unknown;
347+nodeCount?: unknown;
348+ok?: unknown;
349+tabCount?: unknown;
350+tabs?: unknown;
351+targetId?: unknown;
352+} {
353+const details = result?.details as
354+| {
355+externalContent?: { untrusted?: unknown; source?: unknown; kind?: unknown };
356+format?: unknown;
357+messageCount?: unknown;
358+nodeCount?: unknown;
359+ok?: unknown;
360+tabCount?: unknown;
361+tabs?: unknown;
362+targetId?: unknown;
363+}
364+| undefined;
365+expect(details).toBeDefined();
366+if (!details) {
367+throw new Error("Expected browser tool result details");
368+}
369+expect(details?.ok).toBe(true);
370+expect(details?.externalContent?.untrusted).toBe(true);
371+expect(details?.externalContent?.source).toBe("browser");
372+expect(details?.externalContent?.kind).toBe(kind);
373+return details;
374+}
375+333376function nodeInvokeCall(callIndex: number): {
334377options: { timeoutMs?: number };
335378request: {
@@ -1171,27 +1214,12 @@ describe("browser tool external content wrapping", () => {
1171121411721215const tool = createBrowserTool();
11731216const result = await tool.execute?.("call-1", { action: "snapshot", snapshotFormat: "aria" });
1174-expect(result?.content?.[0]).toMatchObject({
1175-type: "text",
1176-text: expect.stringContaining("<<<EXTERNAL_UNTRUSTED_CONTENT"),
1177-});
1178-const ariaTextBlock = result?.content?.[0];
1179-const ariaTextValue =
1180-ariaTextBlock && typeof ariaTextBlock === "object" && "text" in ariaTextBlock
1181- ? (ariaTextBlock as { text?: unknown }).text
1182- : undefined;
1183-const ariaText = typeof ariaTextValue === "string" ? ariaTextValue : "";
1217+const ariaText = firstResultText(result);
1218+expect(ariaText).toContain("<<<EXTERNAL_UNTRUSTED_CONTENT");
11841219expect(ariaText).toContain("Ignore previous instructions");
1185-expect(result?.details).toMatchObject({
1186-ok: true,
1187-format: "aria",
1188-nodeCount: 1,
1189-externalContent: expect.objectContaining({
1190-untrusted: true,
1191-source: "browser",
1192-kind: "snapshot",
1193-}),
1194-});
1220+const details = externalContentDetails(result, "snapshot");
1221+expect(details.format).toBe("aria");
1222+expect(details.nodeCount).toBe(1);
11951223});
1196122411971225it("wraps tabs output as external content", async () => {
@@ -1207,36 +1235,24 @@ describe("browser tool external content wrapping", () => {
1207123512081236const tool = createBrowserTool();
12091237const result = await tool.execute?.("call-1", { action: "tabs" });
1210-expect(result?.content?.[0]).toMatchObject({
1211-type: "text",
1212-text: expect.stringContaining("<<<EXTERNAL_UNTRUSTED_CONTENT"),
1213-});
1214-const tabsTextBlock = result?.content?.[0];
1215-const tabsTextValue =
1216-tabsTextBlock && typeof tabsTextBlock === "object" && "text" in tabsTextBlock
1217- ? (tabsTextBlock as { text?: unknown }).text
1218- : undefined;
1219-const tabsText = typeof tabsTextValue === "string" ? tabsTextValue : "";
1238+const tabsText = firstResultText(result);
1239+expect(tabsText).toContain("<<<EXTERNAL_UNTRUSTED_CONTENT");
12201240expect(tabsText.indexOf("suggestedTargetId")).toBeLessThan(tabsText.indexOf("targetId"));
12211241expect(tabsText).toContain('"suggestedTargetId": "docs"');
12221242expect(tabsText).toContain("Ignore previous instructions");
1223-expect(result?.details).toMatchObject({
1224-ok: true,
1225-tabCount: 1,
1226-tabs: [
1227-expect.objectContaining({
1228-suggestedTargetId: "docs",
1229-tabId: "t1",
1230-label: "docs",
1231-targetId: "RAW-TARGET",
1232-}),
1233-],
1234-externalContent: expect.objectContaining({
1235-untrusted: true,
1236-source: "browser",
1237-kind: "tabs",
1238-}),
1239-});
1243+const details = externalContentDetails(result, "tabs");
1244+expect(details.tabCount).toBe(1);
1245+expect(Array.isArray(details.tabs)).toBe(true);
1246+const [tab] = details.tabs as Array<{
1247+label?: unknown;
1248+suggestedTargetId?: unknown;
1249+tabId?: unknown;
1250+targetId?: unknown;
1251+}>;
1252+expect(tab?.suggestedTargetId).toBe("docs");
1253+expect(tab?.tabId).toBe("t1");
1254+expect(tab?.label).toBe("docs");
1255+expect(tab?.targetId).toBe("RAW-TARGET");
12401256});
1241125712421258it("wraps console output as external content", async () => {
@@ -1250,27 +1266,12 @@ describe("browser tool external content wrapping", () => {
1250126612511267const tool = createBrowserTool();
12521268const result = await tool.execute?.("call-1", { action: "console" });
1253-expect(result?.content?.[0]).toMatchObject({
1254-type: "text",
1255-text: expect.stringContaining("<<<EXTERNAL_UNTRUSTED_CONTENT"),
1256-});
1257-const consoleTextBlock = result?.content?.[0];
1258-const consoleTextValue =
1259-consoleTextBlock && typeof consoleTextBlock === "object" && "text" in consoleTextBlock
1260- ? (consoleTextBlock as { text?: unknown }).text
1261- : undefined;
1262-const consoleText = typeof consoleTextValue === "string" ? consoleTextValue : "";
1269+const consoleText = firstResultText(result);
1270+expect(consoleText).toContain("<<<EXTERNAL_UNTRUSTED_CONTENT");
12631271expect(consoleText).toContain("Ignore previous instructions");
1264-expect(result?.details).toMatchObject({
1265-ok: true,
1266-targetId: "t1",
1267-messageCount: 1,
1268-externalContent: expect.objectContaining({
1269-untrusted: true,
1270-source: "browser",
1271-kind: "console",
1272-}),
1273-});
1272+const details = externalContentDetails(result, "console");
1273+expect(details.targetId).toBe("t1");
1274+expect(details.messageCount).toBe(1);
12741275});
12751276});
12761277@@ -1295,19 +1296,30 @@ describe("browser tool act stale target recovery", () => {
12951296});
1296129712971298expect(browserActionsMocks.browserAct).toHaveBeenCalledTimes(2);
1298-expect(browserActionsMocks.browserAct).toHaveBeenNthCalledWith(
1299+expect(mockCallArg(browserActionsMocks.browserAct, 0, 0)).toBeUndefined();
1300+const firstRequest = mockCallArg<{ kind?: string; ref?: string; targetId?: string }>(
1301+browserActionsMocks.browserAct,
1302+0,
129913031,
1300-undefined,
1301-expect.objectContaining({ targetId: "stale-tab", kind: "hover", ref: "btn-1" }),
1302-expect.objectContaining({ profile: "user" }),
13031304);
1304-expect(browserActionsMocks.browserAct).toHaveBeenNthCalledWith(
1305-2,
1306-undefined,
1307-expect.not.objectContaining({ targetId: expect.anything() }),
1308-expect.objectContaining({ profile: "user" }),
1305+expect(firstRequest.targetId).toBe("stale-tab");
1306+expect(firstRequest.kind).toBe("hover");
1307+expect(firstRequest.ref).toBe("btn-1");
1308+const firstOptions = mockCallArg<{ profile?: string }>(browserActionsMocks.browserAct, 0, 2);
1309+expect(firstOptions.profile).toBe("user");
1310+1311+expect(mockCallArg(browserActionsMocks.browserAct, 1, 0)).toBeUndefined();
1312+const secondRequest = mockCallArg<{ kind?: string; ref?: string; targetId?: string }>(
1313+browserActionsMocks.browserAct,
1314+1,
1315+1,
13091316);
1310-expect(result?.details).toMatchObject({ ok: true });
1317+expect(secondRequest.targetId).toBeUndefined();
1318+expect(secondRequest.kind).toBe("hover");
1319+expect(secondRequest.ref).toBe("btn-1");
1320+const secondOptions = mockCallArg<{ profile?: string }>(browserActionsMocks.browserAct, 1, 2);
1321+expect(secondOptions.profile).toBe("user");
1322+expect((result?.details as { ok?: unknown } | undefined)?.ok).toBe(true);
13111323});
1312132413131325it("does not retry mutating user-browser act requests without targetId", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。