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
8 changes: 4 additions & 4 deletions ts/packages/agentRpc/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ export async function createAgentRpcClient(
actionContextId: actionContextMap.getId(actionContext),
activityContext: actionContext.activityContext,
isFromReasoningLoop: actionContext.isFromReasoningLoop,
workingDirectory: actionContext.workingDirectory,
...getContextParam(actionContext.sessionContext),
});
} finally {
Expand All @@ -265,15 +266,14 @@ export async function createAgentRpcClient(
}
async function withActionContextAsync<T>(
actionContext: ActionContext<ShimContext>,
fn: (contextParams: {
actionContextId: number;
isFromReasoningLoop: boolean;
}) => Promise<T>,
fn: (contextParams: ActionContextParams) => Promise<T>,
) {
try {
return await fn({
actionContextId: actionContextMap.getId(actionContext),
activityContext: actionContext.activityContext,
isFromReasoningLoop: actionContext.isFromReasoningLoop,
workingDirectory: actionContext.workingDirectory,
...getContextParam(actionContext.sessionContext),
});
} finally {
Expand Down
1 change: 1 addition & 0 deletions ts/packages/agentRpc/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,7 @@ export function createAgentRpcServer(
streamingContext: undefined,
activityContext: param.activityContext,
isFromReasoningLoop: param.isFromReasoningLoop ?? false,
workingDirectory: param.workingDirectory,
get abortSignal() {
return abortController.signal;
},
Expand Down
1 change: 1 addition & 0 deletions ts/packages/agentRpc/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ export type ActionContextParams = ContextParams & {
actionContextId: number;
activityContext: ActivityContext | undefined;
isFromReasoningLoop: boolean;
workingDirectory: string | undefined;
};

export type OptionsFunctionCallBack = {
Expand Down
84 changes: 84 additions & 0 deletions ts/packages/agentRpc/test/actionContext.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import type {
ActionContext,
AppAgent,
SessionContext,
} from "@typeagent/agent-sdk";
import { createAgentRpcClient } from "../src/client.js";
import {
createChannelProviderAdapter,
type ChannelProviderAdapter,
} from "../src/common.js";
import { createAgentRpcServer } from "../src/server.js";

describe("agent action context RPC", () => {
test("propagates workingDirectory to the out-of-process agent", async () => {
let clientProvider: ChannelProviderAdapter;
let serverProvider: ChannelProviderAdapter;
clientProvider = createChannelProviderAdapter(
"test-client",
(message, callback) => {
queueMicrotask(() => serverProvider.notifyMessage(message));
callback?.(null);
},
);
serverProvider = createChannelProviderAdapter(
"test-server",
(message, callback) => {
queueMicrotask(() => clientProvider.notifyMessage(message));
callback?.(null);
},
);

let receivedWorkingDirectory: string | undefined;
const serverAgent: AppAgent = {
initializeAgentContext: async () => ({}),
executeAction: async (_action, context) => {
receivedWorkingDirectory = context.workingDirectory;
return undefined;
},
};
const server = createAgentRpcServer(
"test",
serverAgent,
serverProvider,
);
const clientAgent = await createAgentRpcClient(
"test",
clientProvider,
server.agentInterface,
);

try {
const agentContext = await clientAgent.initializeAgentContext?.();
const sessionContext = {
agentContext,
sessionContextId: "rpc-working-directory-test",
} as SessionContext<unknown>;
const actionContext = {
sessionContext,
workingDirectory: "C:\\host-authorized-workspace",
isFromReasoningLoop: false,
} as ActionContext<unknown>;

await clientAgent.executeAction?.(
{
schemaName: "test",
actionName: "test",
parameters: {},
},
actionContext,
);

expect(receivedWorkingDirectory).toBe(
"C:\\host-authorized-workspace",
);
} finally {
server.closeFn();
clientProvider.notifyDisconnected();
serverProvider.notifyDisconnected();
}
});
});
3 changes: 3 additions & 0 deletions ts/packages/agentSdk/src/agentInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,9 @@ export interface ActionContext<T = void> {
// to execute immediately or redirect back to the reasoning loop.
readonly isFromReasoningLoop: boolean;

// Absolute filesystem root authorized by the host for this action.
readonly workingDirectory?: string | undefined;

// queue up toggle transient agent to be executed at the end of the commands
queueToggleTransientAgent(
agentName: string,
Expand Down
Loading
Loading