Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ jobs:
- name: Build
run: npm run build

- name: Type-check test sources
run: npm run typecheck:test

- name: Format check
run: npm run format

Expand Down
69 changes: 67 additions & 2 deletions A365_DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ Use scopes when you want explicit spans for agent, tool, inference, or output wo

```typescript
import {
ExecuteToolCallArguments,
ExecuteToolCallResult,
ExecuteToolScope,
InferenceOperationType,
InferenceScope,
InvokeAgentScope,
ToolCallAction,
ToolCallOutcomeStatus,
ToolPolicyDecision,
} from "@microsoft/opentelemetry";

const invokeScope = InvokeAgentScope.start(
Expand All @@ -36,9 +41,36 @@ const invokeScope = InvokeAgentScope.start(
);

invokeScope.run(async () => {
const toolArguments = new ExecuteToolCallArguments({
action: ToolCallAction.READ,
resources: [
{
id: "drive-item-1",
uri: "https://contoso.example/items/1",
name: "Quarterly plan",
type: "document",
provider: "sharepoint",
identifiers: [{ type: "driveItem", value: "1" }],
container: {
id: "folder-1",
uri: "https://contoso.example/folders/1",
type: "folder",
},
custom_resource_field: "kept",
},
],
parameters: { query: "hello", includeArchived: false },
custom_argument_field: "kept",
});

const toolScope = ExecuteToolScope.start(
{ conversationId: "conv-123", sessionId: "session-456" },
{ toolName: "Search", input: { query: "hello" } },
{
toolName: "Search",
arguments: toolArguments,
toolCallId: "tool-call-123",
toolType: "function",
},
{ agentId: "agent-1", tenantId: "tenant-1" },
);

Expand All @@ -48,6 +80,36 @@ invokeScope.run(async () => {
{ agentId: "agent-1", tenantId: "tenant-1" },
);

toolScope.recordResponse(
new ExecuteToolCallResult({
outcome: {
status: ToolCallOutcomeStatus.SUCCESS,
code: "200",
message: "Completed",
},
resources: [
{
id: "drive-item-1",
name: "Quarterly plan",
type: "document",
outcome: {
status: ToolCallOutcomeStatus.SUCCESS,
code: "200",
},
policy: {
decision: ToolPolicyDecision.ALLOW,
id: "policy-1",
name: "AllowDocumentRead",
},
data: { snippetCount: 3 },
custom_result_field: "kept",
},
],
pagination: { has_more: false, total_count: 1 },
custom_result_field: "kept",
}),
);

toolScope.dispose();
inferenceScope.dispose();
});
Expand All @@ -62,6 +124,9 @@ invokeScope.recordResponseParameters({
invokeScope.dispose();
```

`ExecuteToolScope` serializes arguments to `gen_ai.tool.call.arguments` and results to
`gen_ai.tool.call.result` as JSON span attributes, so they may contain sensitive data.

`InvokeAgentScope`, `InferenceScope`, and `ExecuteToolScope` accept `request.sessionId`.
When you provide it, those scopes write `microsoft.session.id` directly on the created
span instead of relying on later baggage enrichment. `OutputScope` does not currently
Expand Down Expand Up @@ -90,9 +155,9 @@ captures response and usage values after the agent completes.
| `responseParameters.cacheWriteInputTokens` | `gen_ai.usage.cache_write.input_tokens` |
| `responseParameters.cacheReadInputTokens` | `gen_ai.usage.cache_read.input_tokens` |
| `agentDetails.providerName` | `gen_ai.provider.name` |

System instructions may contain sensitive content. Only capture them when you
intend to store prompt text and have reviewed downstream access controls.
intend to store prompt text and have reviewed downstream access controls.

## Baggage And Context

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [Unreleased]

### Features Added
- Add typed, extensible ExecuteTool argument and result schemas with default schema_version: "1.0". [#240](https://github.com/microsoft/opentelemetry-distro-javascript/pull/240)
- Add manual `sessionId` propagation to `ExecuteToolScope` and `InferenceScope`, plus opt-in custom baggage enrichment for recognized GenAI spans through `BaggageBuilder.customAttribute()` and `customAttributes()`. [#242](https://github.com/microsoft/opentelemetry-distro-javascript/pull/242)
- Add GenAI v1.42 InvokeAgent request, response, cache-token, and provider attribute capture for manual A365 scopes. [#239](https://github.com/microsoft/opentelemetry-distro-javascript/pull/239)

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"test:unit": "vitest run --config vitest.unit.config.ts",
"test:functional": "vitest run --config vitest.functional.config.ts",
"test:integration": "node --test test/integration/*.test.mjs",
"typecheck:test": "tsc -p tsconfig.type-tests.json --noEmit",
"test:performance": "node --expose-gc perf/benchmark.mjs",
"test:esm-build": "node --input-type=module -e \"import('./dist/esm/distro/instrumentations.js').then(()=>console.log('esm import ok')).catch((e)=>{console.error(e);process.exit(1);})\"",
"test:watch": "vitest",
Expand Down
24 changes: 22 additions & 2 deletions src/a365/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,26 @@
*/

import type { SpanKind, TimeInput, Link, Context, TraceState } from "@opentelemetry/api";
import type { ExecuteToolCallArguments } from "./tool-call-models.js";

export {
ToolCallAction,
ToolCallOutcomeStatus,
ToolPolicyDecision,
ExecuteToolCallArguments,
ExecuteToolCallResult,
} from "./tool-call-models.js";
export type {
ToolCallIdentifier,
ToolCallContainer,
ToolCallResource,
ToolCallResultOutcome,
ToolCallResultSensitivity,
ToolCallResultPolicy,
ToolCallResultSecurity,
ToolCallResultPagination,
ToolCallResultResource,
} from "./tool-call-models.js";

// ---------------------------------------------------------------------------
// Default finish reason (per OTel spec)
Expand Down Expand Up @@ -422,8 +442,8 @@ export interface InvokeAgentScopeDetails {
export interface ToolCallDetails {
/** Name of the tool being called (required). */
toolName: string;
/** Arguments passed to the tool, as an object or serialized string. */
arguments?: Record<string, unknown> | string;
/** Arguments passed to the tool, as an object, execute-tool schema model, or serialized string. */
arguments?: Record<string, unknown> | ExecuteToolCallArguments | string;
/** Unique identifier of the tool call. */
toolCallId?: string;
/** Human-readable description of the tool. */
Expand Down
14 changes: 14 additions & 0 deletions src/a365/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export {
InvocationRole,
InferenceOperationType,
DEFAULT_FINISH_REASON,
ToolCallAction,
ToolCallOutcomeStatus,
ToolPolicyDecision,
ExecuteToolCallArguments,
ExecuteToolCallResult,
GuardrailDecisionType,
GuardrailRiskSeverity,
GuardrailTargetType,
Expand All @@ -56,6 +61,15 @@ export type {
ToolCallRequestPart,
ToolCallResponsePart,
ReasoningPart,
ToolCallIdentifier,
ToolCallContainer,
ToolCallResource,
ToolCallResultOutcome,
ToolCallResultSensitivity,
ToolCallResultPolicy,
ToolCallResultSecurity,
ToolCallResultPagination,
ToolCallResultResource,
AgentDetails,
UserDetails,
CallerDetails,
Expand Down
36 changes: 35 additions & 1 deletion src/a365/message-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,21 @@ import type {
OutputMessagesParam,
SystemInstructionPart,
} from "./contracts.js";
import { MessageRole, DEFAULT_FINISH_REASON } from "./contracts.js";
import {
DEFAULT_FINISH_REASON,
ExecuteToolCallArguments,
ExecuteToolCallResult,
MessageRole,
} from "./contracts.js";

const EXECUTE_TOOL_SERIALIZATION_ERROR =
'{"serialization_error":"Failed to serialize execute tool payload."}';

function isTypedExecuteToolPayload(
value: object,
): value is ExecuteToolCallArguments | ExecuteToolCallResult {
return value instanceof ExecuteToolCallArguments || value instanceof ExecuteToolCallResult;
}

/**
* Type guard that returns `true` when the input is a structured wrapper
Expand Down Expand Up @@ -106,6 +120,26 @@ export function serializeMessages(wrapper: InputMessages | OutputMessages): stri
}
}

/**
* Serializes execute-tool payload objects while keeping telemetry recording non-throwing.
* Returns `undefined` for nullish payloads so callers can omit the attribute.
*/
export function serializeToolPayload(value: object | null | undefined): string | undefined {
if (value == null) {
return undefined;
}

if (!isTypedExecuteToolPayload(value)) {
return safeSerializeToJson(value as Record<string, unknown>, "payload");
}

try {
return JSON.stringify(value) ?? EXECUTE_TOOL_SERIALIZATION_ERROR;
} catch {
return EXECUTE_TOOL_SERIALIZATION_ERROR;
}
}

/**
* Serializes system instruction parts to a JSON array.
*
Expand Down
17 changes: 13 additions & 4 deletions src/a365/scopes/ExecuteToolScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
import { SpanKind } from "@opentelemetry/api";
import { OpenTelemetryScope } from "./OpenTelemetryScope.js";
import { OpenTelemetryConstants } from "../constants.js";
import { safeSerializeToJson } from "../message-utils.js";
import { safeSerializeToJson, serializeToolPayload } from "../message-utils.js";
import type {
ToolCallDetails,
AgentDetails,
ExecuteToolCallResult,
UserDetails,
Request,
SpanDetails,
Expand Down Expand Up @@ -65,7 +66,9 @@ export class ExecuteToolScope extends OpenTelemetryScope {
this.setTagMaybe(OpenTelemetryConstants.GEN_AI_TOOL_NAME_KEY, toolName);
this.setTagMaybe(
OpenTelemetryConstants.GEN_AI_TOOL_ARGS_KEY,
args != null ? safeSerializeToJson(args, "arguments") : undefined,
typeof args === "string"
? safeSerializeToJson(args, "arguments")
: serializeToolPayload(args),
);
this.setTagMaybe(OpenTelemetryConstants.GEN_AI_TOOL_TYPE_KEY, toolType);
this.setTagMaybe(OpenTelemetryConstants.GEN_AI_TOOL_CALL_ID_KEY, toolCallId);
Expand All @@ -88,10 +91,16 @@ export class ExecuteToolScope extends OpenTelemetryScope {
* Records response information for telemetry tracking.
* Objects are serialized to JSON automatically.
*/
public recordResponse(response: Record<string, unknown> | string): void {
public recordResponse(response: ExecuteToolCallResult | null | undefined): void;
public recordResponse(response: Record<string, unknown> | string): void;
public recordResponse(
response: Record<string, unknown> | ExecuteToolCallResult | string | null | undefined,
): void {
this.setTagMaybe(
OpenTelemetryConstants.GEN_AI_TOOL_CALL_RESULT_KEY,
safeSerializeToJson(response, "result"),
typeof response === "string"
? safeSerializeToJson(response, "result")
: serializeToolPayload(response),
);
}
}
Loading
Loading