Skip to content

Commit 4b21959

Browse files
committed
feat(dashboard-agent): locate tool replaces the not-found sweep
1 parent 26f9a3a commit 4b21959

7 files changed

Lines changed: 181 additions & 37 deletions

File tree

internal-packages/dashboard-agent/src/__snapshots__/prompt-prefix.test.ts.snap

Lines changed: 16 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal-packages/dashboard-agent/src/dashboard-agent.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,6 +1349,7 @@ describe("buildDashboardAgentTools", () => {
13491349
"list_projects",
13501350
"list_runs",
13511351
"list_tasks",
1352+
"locate",
13521353
"navigate_to",
13531354
"run_query",
13541355
"render_view",

internal-packages/dashboard-agent/src/prompt-prefix.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ describe("the head-start and agent prefixes are the same prefix", () => {
8888
* drift. The snapshot below is the itemised diff a reviewer reads.
8989
*/
9090
const PREFIX_BUDGET = {
91-
assistant: { chars: 81_300, estimatedTokens: 20_600, tools: 24, promptChars: 27_800 },
92-
code: { chars: 88_900, estimatedTokens: 22_500, tools: 28, promptChars: 30_300 },
91+
assistant: { chars: 79_400, estimatedTokens: 20_100, tools: 25, promptChars: 27_500 },
92+
code: { chars: 87_000, estimatedTokens: 22_000, tools: 29, promptChars: 30_100 },
9393
} as const;
9494

9595
describe("the prefix stays inside its budget", () => {

internal-packages/dashboard-agent/src/tool-api.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
listProjectsSchema,
1717
listRunsSchema,
1818
listTasksSchema,
19+
locateSchema,
1920
renderViewSchema,
2021
runQuerySchema,
2122
searchDocsSchema,
@@ -215,6 +216,38 @@ export function withLiveState(metrics: unknown, queueType: "task" | "custom", li
215216
};
216217
}
217218

219+
/**
220+
* The org-wide locator. A user-level route, so it spends the delegated token directly, and
221+
* the organization comes only from that token's claim — never from the model.
222+
*/
223+
export function buildLocateTool(args: {
224+
ctx: DashboardAgentToolContext;
225+
client: DashboardAgentApiClient;
226+
}): ToolSet {
227+
const { userActorToken } = args.ctx;
228+
const { origin, hasAuth } = args.client;
229+
return {
230+
locate: tool({
231+
...locateSchema,
232+
execute: async ({ kind, id }) => {
233+
if (!hasAuth) return NO_AUTH;
234+
const result = await apiGet(
235+
origin,
236+
`/api/v1/locate/${kind}/${encodeURIComponent(id)}`,
237+
userActorToken!
238+
);
239+
// A failed locate is not a `found: false`: it never proves absence.
240+
if (!result.ok) {
241+
return {
242+
error: `Couldn't locate ${kind} ${id}${fetchReason(result)}. That is not evidence it doesn't exist.`,
243+
};
244+
}
245+
return result.data;
246+
},
247+
}),
248+
};
249+
}
250+
218251
/** Failed `run_query` calls in a row before the tool tells the model to stop and answer. */
219252
export const MAX_CONSECUTIVE_QUERY_FAILURES = 3;
220253

@@ -774,7 +807,7 @@ export function buildApiTools(args: {
774807
if ("status" in result && result.status === 404) {
775808
const scope = target ? "that project/environment" : "the current environment";
776809
return {
777-
error: `No commit found for run ${runId} in ${scope}. That is not evidence the run isn't locked to a deployment — sweep (list_projects, then get_run with project/environment) before concluding, then retry this call with project/environment for wherever it's found.`,
810+
error: `No commit found for run ${runId} in ${scope}. That is not evidence the run isn't locked to a deployment — call locate before concluding, then retry this call with the project/environment it names.`,
778811
};
779812
}
780813
return { error: `Couldn't resolve the commit for ${runId}${fetchReason(result)}.` };
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2+
import { buildLocateTool } from "./tool-api";
3+
import { createApiClient } from "./tool-api-client";
4+
5+
/**
6+
* The org-wide locator replaces the model-driven not-found sweep, so what it hands back
7+
* has to keep "not here" and "couldn't look" apart: only the route's own `found: false`
8+
* is an absence.
9+
*/
10+
11+
const ORIGIN = "https://api.example.com";
12+
13+
let calls: Array<{ url: string; auth: string | null }> = [];
14+
15+
function stubFetch(reply: (url: string) => Response) {
16+
return vi.fn(async (input: any, init: any = {}) => {
17+
const url = typeof input === "string" ? input : input.url;
18+
calls.push({ url, auth: new Headers(init.headers ?? {}).get("authorization") });
19+
return reply(url);
20+
});
21+
}
22+
23+
function locate(overrides: Record<string, unknown> = {}) {
24+
const ctx = {
25+
userActorToken: "uat",
26+
apiOrigin: ORIGIN,
27+
projectRef: "proj_current",
28+
environmentName: "prod",
29+
...overrides,
30+
};
31+
return buildLocateTool({ ctx, client: createApiClient(ctx) }).locate as any;
32+
}
33+
34+
beforeEach(() => (calls = []));
35+
afterEach(() => vi.unstubAllGlobals());
36+
37+
describe("locate", () => {
38+
it("asks the org-wide route with the delegated token and passes the scopes through", async () => {
39+
const found = {
40+
found: true,
41+
checked: "organization",
42+
scopes: [
43+
{
44+
projectRef: "proj_other",
45+
projectName: "Other",
46+
environmentName: "preview",
47+
environmentType: "PREVIEW",
48+
branchName: "feat/x",
49+
targetable: true,
50+
},
51+
],
52+
};
53+
vi.stubGlobal(
54+
"fetch",
55+
stubFetch(() => Response.json(found))
56+
);
57+
58+
const result = await locate().execute({ kind: "run", id: "run_abc" }, {} as any);
59+
60+
expect(calls).toEqual([{ url: `${ORIGIN}/api/v1/locate/run/run_abc`, auth: "Bearer uat" }]);
61+
expect(result).toEqual(found);
62+
});
63+
64+
it("passes a not-found through verbatim", async () => {
65+
vi.stubGlobal(
66+
"fetch",
67+
stubFetch(() => Response.json({ found: false, checked: "organization" }))
68+
);
69+
70+
expect(await locate().execute({ kind: "error", id: "error_abc" }, {} as any)).toEqual({
71+
found: false,
72+
checked: "organization",
73+
});
74+
expect(calls[0].url).toBe(`${ORIGIN}/api/v1/locate/error/error_abc`);
75+
});
76+
77+
it("reports a 401 or 403 as a failed lookup, never as an absence", async () => {
78+
for (const status of [401, 403]) {
79+
calls = [];
80+
vi.stubGlobal(
81+
"fetch",
82+
stubFetch(() => new Response("", { status }))
83+
);
84+
85+
const result = await locate().execute({ kind: "run", id: "run_abc" }, {} as any);
86+
87+
expect(result).toEqual({
88+
error: `Couldn't locate run run_abc (status ${status}). That is not evidence it doesn't exist.`,
89+
});
90+
}
91+
});
92+
93+
it("says so when the turn has no delegated access", async () => {
94+
vi.stubGlobal(
95+
"fetch",
96+
stubFetch(() => Response.json({}))
97+
);
98+
99+
const result = await locate({ userActorToken: undefined }).execute(
100+
{ kind: "run", id: "run_abc" },
101+
{} as any
102+
);
103+
104+
expect(result).toEqual({ error: "No delegated access is available for this turn." });
105+
expect(calls).toEqual([]);
106+
});
107+
});

0 commit comments

Comments
 (0)