feat(ai-builder): Trace the resources the editor attaches to a message (no-changelog) (#35475)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
José Braulio González Valido 2026-08-04 15:58:51 +01:00 committed by GitHub
parent 1f2b96f330
commit b6afb3bdbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 43 additions and 12 deletions

View File

@ -3914,6 +3914,14 @@ describe('InstanceAiService — editor handoff context resources', () => {
expect(source).toContain('buildContextResourcesBlock(contextAttachments)');
expect(source).not.toContain('buildContextResourcesBlock(workflowAttachments)');
});
it('traces the attached resources, which the raw message no longer shows', () => {
const source = InstanceAiService.toString();
// Assignment-form tolerant: the wiring is what matters, not whether the
// field is spread into the literal or set on it afterwards.
expect(source).toMatch(/resourceAttachments\s*[:=]\s*contextAttachments\.map/);
});
});
describe('InstanceAiService — workflow verification follow-up gate', () => {

View File

@ -200,6 +200,14 @@ function getErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
/** A resource attachment as the trace records it: the reference, not its contents. */
type TracedResourceAttachment = {
type: InstanceAiResourceAttachment['type'];
id: string;
projectId?: string;
executionId?: string;
};
/** Root-run outputs for a suspended segment — keep the LangSmith turn readable (AGENT-371). */
function buildSuspensionTraceOutputs(runId: string, suspension: SuspensionInfo | undefined) {
const rawMessage = suspension?.suspendPayload.message;
@ -3295,18 +3303,33 @@ export class InstanceAiService {
errorReporterExecutionToken = this.instanceAiErrorReporter.beginRun(runId);
messageId = nanoid();
const traceInput: Record<string, unknown> = {
message,
...(fileAttachments.length
? {
attachments: fileAttachments.map((attachment) => ({
mimeType: attachment.mimeType,
size: attachment.data.length,
})),
}
: {}),
...(messageGroupId ? { messageGroupId } : {}),
};
const traceInput: Record<string, unknown> = { message };
if (fileAttachments.length) {
traceInput.attachments = fileAttachments.map((attachment) => ({
mimeType: attachment.mimeType,
size: attachment.data.length,
}));
}
// `message` is the user's raw text, so without this the trace has no
// record that the editor handed the agent a resource.
if (contextAttachments.length) {
traceInput.resourceAttachments = contextAttachments.map((attachment) => {
const resource: TracedResourceAttachment = {
type: attachment.type,
id: attachment.id,
};
if (attachment.type === 'agent') {
resource.projectId = attachment.projectId;
}
if (attachment.type === 'workflow' && attachment.executionId) {
resource.executionId = attachment.executionId;
}
return resource;
});
}
if (messageGroupId) {
traceInput.messageGroupId = messageGroupId;
}
// Shared with createExecutionEnvironment so one ProxyTokenManager backs tracing + the run.
const proxyRunConfig = await this.createProxyRunConfig(user);