Skip to content

Commit 3970fff

Browse files
authored
feat: handle undefined github app context in scout (#93)
1 parent 45974e3 commit 3970fff

File tree

7 files changed

+45
-59
lines changed

7 files changed

+45
-59
lines changed

packages/scout-agent/agent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ agent.on("request", async (request) => {
3636
});
3737

3838
agent.on("chat", async ({ id, messages }) => {
39-
const params = scout.buildStreamTextParams({
39+
const params = await scout.buildStreamTextParams({
4040
messages,
4141
chatID: id,
4242
model: "anthropic/claude-sonnet-4.5",

packages/scout-agent/lib/compute/tools.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { WORKSPACE_INFO_KEY } from "./common";
99

1010
export const createComputeTools = <T>({
1111
agent,
12-
getGithubAppContext,
12+
githubAppContext,
1313
initializeWorkspace,
1414
createWorkspaceClient,
1515
}: {
@@ -19,10 +19,10 @@ export const createComputeTools = <T>({
1919
) => Promise<{ workspaceInfo: T; message: string }>;
2020
createWorkspaceClient: (workspaceInfo: T) => Promise<Client>;
2121
/**
22-
* A function that returns the GitHub auth context for Git authentication.
22+
* The GitHub auth context for Git authentication.
2323
* If provided, the workspace_authenticate_git tool will be available.
2424
*/
25-
getGithubAppContext?: () => Promise<github.AppAuthOptions>;
25+
githubAppContext?: github.AppAuthOptions;
2626
}): Record<string, Tool> => {
2727
const newClient = async () => {
2828
const workspaceInfo = await agent.store.get(WORKSPACE_INFO_KEY);
@@ -56,7 +56,7 @@ export const createComputeTools = <T>({
5656
},
5757
}),
5858

59-
...(getGithubAppContext
59+
...(githubAppContext
6060
? {
6161
workspace_authenticate_git: tool({
6262
description: `Authenticate with Git repositories for push/pull operations. Call this before any Git operations that require authentication.
@@ -74,13 +74,6 @@ It's safe to call this multiple times - re-authenticating is perfectly fine and
7474
execute: async (args, _opts) => {
7575
const client = await newClient();
7676

77-
// Here we generate a GitHub token scoped to the repositories.
78-
const githubAppContext = await getGithubAppContext();
79-
if (!githubAppContext) {
80-
throw new Error(
81-
"You can only use public repositories in this context."
82-
);
83-
}
8477
const token = await github.authenticateApp({
8578
...githubAppContext,
8679
// TODO: We obviously need to handle owner at some point.

packages/scout-agent/lib/core.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ const newAgent = (options: {
6767
return new Response("Hello, world!", { status: 200 });
6868
});
6969
agent.on("chat", async ({ messages }) => {
70-
const params = core.buildStreamTextParams({
70+
const params = await core.buildStreamTextParams({
7171
model: options.model,
7272
messages,
7373
chatID: "b485db32-3d53-45fb-b980-6f4672fc66a6",
@@ -363,7 +363,7 @@ test("buildStreamTextParams honors getGithubAppContext param", async () => {
363363
},
364364
});
365365

366-
const params = scout.buildStreamTextParams({
366+
const params = await scout.buildStreamTextParams({
367367
chatID: "test-chat-id" as blink.ID,
368368
messages: [],
369369
model: newMockModel({ textResponse: "test" }),
@@ -576,7 +576,7 @@ describe("daytona integration", () => {
576576
},
577577
});
578578

579-
const params = scout.buildStreamTextParams({
579+
const params = await scout.buildStreamTextParams({
580580
chatID: "test-chat-id" as blink.ID,
581581
messages: [],
582582
model: newMockModel({ textResponse: "test" }),
@@ -643,7 +643,7 @@ describe("daytona integration", () => {
643643
},
644644
});
645645

646-
const params = scout.buildStreamTextParams({
646+
const params = await scout.buildStreamTextParams({
647647
chatID: "test-chat-id" as blink.ID,
648648
messages: [],
649649
model: newMockModel({ textResponse: "test" }),
@@ -735,7 +735,7 @@ describe("daytona integration", () => {
735735
},
736736
});
737737

738-
const params = scout.buildStreamTextParams({
738+
const params = await scout.buildStreamTextParams({
739739
chatID: "test-chat-id" as blink.ID,
740740
messages: [],
741741
model: newMockModel({ textResponse: "test" }),

packages/scout-agent/lib/core.ts

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export interface BuildStreamTextParamsOptions {
4747
* A function that returns the GitHub auth context for the GitHub tools and for Git authentication inside workspaces.
4848
* If not provided, the GitHub auth context will be created using the app ID and private key from the GitHub config.
4949
*/
50-
getGithubAppContext?: () => Promise<github.AppAuthOptions>;
50+
getGithubAppContext?: () => Promise<github.AppAuthOptions | undefined>;
5151
}
5252

5353
interface Logger {
@@ -264,23 +264,34 @@ export class Scout {
264264
}
265265
}
266266

267-
buildStreamTextParams({
267+
async buildStreamTextParams({
268268
messages,
269269
chatID,
270270
model,
271271
providerOptions,
272272
tools: providedTools,
273273
getGithubAppContext,
274274
systemPrompt = defaultSystemPrompt,
275-
}: BuildStreamTextParamsOptions): {
275+
}: BuildStreamTextParamsOptions): Promise<{
276276
model: LanguageModel;
277277
messages: ModelMessage[];
278278
maxOutputTokens: number;
279279
providerOptions?: ProviderOptions;
280280
tools: Tools;
281-
} {
281+
}> {
282282
this.printConfigWarnings();
283283

284+
// Resolve the GitHub app context once for all tools
285+
const githubAppContext = this.github.config
286+
? await (
287+
getGithubAppContext ??
288+
githubAppContextFactory({
289+
appId: this.github.config.appID,
290+
privateKey: this.github.config.privateKey,
291+
})
292+
)()
293+
: undefined;
294+
284295
const slackMetadata = getSlackMetadata(messages);
285296
const respondingInSlack =
286297
this.slack.app !== undefined && slackMetadata !== undefined;
@@ -291,13 +302,7 @@ export class Scout {
291302
case "docker": {
292303
computeTools = createComputeTools<DockerWorkspaceInfo>({
293304
agent: this.agent,
294-
getGithubAppContext: this.github.config
295-
? (getGithubAppContext ??
296-
githubAppContextFactory({
297-
appId: this.github.config.appID,
298-
privateKey: this.github.config.privateKey,
299-
}))
300-
: undefined,
305+
githubAppContext,
301306
initializeWorkspace: initializeDockerWorkspace,
302307
createWorkspaceClient: getDockerWorkspaceClient,
303308
});
@@ -307,13 +312,7 @@ export class Scout {
307312
const opts = computeConfig.options;
308313
computeTools = createComputeTools<DaytonaWorkspaceInfo>({
309314
agent: this.agent,
310-
getGithubAppContext: this.github.config
311-
? (getGithubAppContext ??
312-
githubAppContextFactory({
313-
appId: this.github.config.appID,
314-
privateKey: this.github.config.privateKey,
315-
}))
316-
: undefined,
315+
githubAppContext,
317316
initializeWorkspace: (info) =>
318317
initializeDaytonaWorkspace(
319318
this.logger,
@@ -363,13 +362,7 @@ export class Scout {
363362
? createGitHubTools({
364363
agent: this.agent,
365364
chatID,
366-
getGithubAppContext:
367-
getGithubAppContext !== undefined
368-
? getGithubAppContext
369-
: githubAppContextFactory({
370-
appId: this.github.config.appID,
371-
privateKey: this.github.config.privateKey,
372-
}),
365+
githubAppContext,
373366
})
374367
: undefined),
375368
...computeTools,

packages/scout-agent/lib/github.test.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -187,13 +187,12 @@ const withGitHubBotLogin = (login: string) => {
187187
return withEnvVariable("GITHUB_BOT_LOGIN", login);
188188
};
189189

190-
const getGithubAppContextFactory =
191-
(args: { appId: string; privateKey: string }) => async () => {
192-
return {
193-
appId: args.appId,
194-
privateKey: Buffer.from(args.privateKey, "base64").toString("utf-8"),
195-
};
190+
const makeGithubAppContext = (args: { appId: string; privateKey: string }) => {
191+
return {
192+
appId: args.appId,
193+
privateKey: Buffer.from(args.privateKey, "base64").toString("utf-8"),
196194
};
195+
};
197196

198197
describe("defaultGetGithubAppContextFactory", () => {
199198
test("decodes base64 private key", async () => {
@@ -248,7 +247,7 @@ describe("createGitHubTools", () => {
248247
const tools = createGitHubTools({
249248
agent,
250249
chatID: "test-chat-id" as blink.ID,
251-
getGithubAppContext: getGithubAppContextFactory({
250+
githubAppContext: makeGithubAppContext({
252251
appId: "app-id",
253252
privateKey: Buffer.from("key").toString("base64"),
254253
}),
@@ -317,7 +316,7 @@ describe("createGitHubTools", () => {
317316
const tools = createGitHubTools({
318317
agent,
319318
chatID,
320-
getGithubAppContext: getGithubAppContextFactory({
319+
githubAppContext: makeGithubAppContext({
321320
appId: "12345",
322321
privateKey: TEST_RSA_PRIVATE_KEY_BASE64,
323322
}),
@@ -407,7 +406,7 @@ describe("createGitHubTools", () => {
407406
const tools = createGitHubTools({
408407
agent,
409408
chatID: "chat-id" as blink.ID,
410-
getGithubAppContext: getGithubAppContextFactory({
409+
githubAppContext: makeGithubAppContext({
411410
appId: "12345",
412411
privateKey: TEST_RSA_PRIVATE_KEY_BASE64,
413412
}),
@@ -508,7 +507,7 @@ describe("createGitHubTools", () => {
508507
const tools = createGitHubTools({
509508
agent,
510509
chatID: "chat" as blink.ID,
511-
getGithubAppContext: getGithubAppContextFactory({
510+
githubAppContext: makeGithubAppContext({
512511
appId: "12345",
513512
privateKey: TEST_RSA_PRIVATE_KEY_BASE64,
514513
}),

packages/scout-agent/lib/github.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,26 @@ export const githubAppContextFactory = ({
2222
export const createGitHubTools = ({
2323
agent,
2424
chatID,
25-
getGithubAppContext,
25+
githubAppContext,
2626
}: {
2727
agent: blink.Agent<UIMessage>;
2828
chatID: blink.ID;
29-
getGithubAppContext: () => Promise<github.AppAuthOptions>;
29+
githubAppContext: github.AppAuthOptions | undefined;
3030
}): Record<string, Tool> => {
3131
return {
3232
...blink.tools.prefix(
33-
blink.tools.withContext(github.tools, {
34-
appAuth: getGithubAppContext,
35-
}),
33+
githubAppContext
34+
? blink.tools.withContext(github.tools, {
35+
appAuth: githubAppContext,
36+
})
37+
: github.tools,
3638
"github_"
3739
),
3840

3941
github_create_pull_request: tool({
4042
description: github.tools.create_pull_request.description,
4143
inputSchema: github.tools.create_pull_request.inputSchema,
4244
execute: async (args, { abortSignal }) => {
43-
const githubAppContext = await getGithubAppContext();
4445
if (!githubAppContext) {
4546
throw new Error(
4647
"You are not authorized to use this tool in this context."

packages/scout-agent/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@blink-sdk/scout-agent",
33
"description": "A general-purpose AI agent with GitHub, Slack, web search, and compute capabilities built on Blink SDK.",
4-
"version": "0.0.5",
4+
"version": "0.0.6",
55
"type": "module",
66
"keywords": [
77
"blink",

0 commit comments

Comments
 (0)