Skip to content

Commit c025835

Browse files
authored
fix(angular): Correctly pass frontend tool params to core (#22)
1 parent 7c4a6cd commit c025835

File tree

8 files changed

+331
-554
lines changed

8 files changed

+331
-554
lines changed

apps/angular/demo-server/src/index.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,13 @@
11
import { serve } from "@hono/node-server";
22
import { Hono } from "hono";
33
import { cors } from "hono/cors";
4-
import {
5-
CopilotRuntime,
6-
createCopilotEndpoint,
7-
InMemoryAgentRunner,
8-
} from "@copilotkitnext/runtime";
9-
import {
10-
OpenAIAgent,
11-
SlowToolCallStreamingAgent,
12-
} from "@copilotkitnext/demo-agents";
4+
import { CopilotRuntime, createCopilotEndpoint, InMemoryAgentRunner } from "@copilotkitnext/runtime";
5+
import { OpenAIAgent, SlowToolCallStreamingAgent } from "@copilotkitnext/demo-agents";
136

147
const runtime = new CopilotRuntime({
158
agents: {
169
// @ts-ignore
17-
default: new SlowToolCallStreamingAgent(),
10+
default: new OpenAIAgent(),
1811
},
1912
runner: new InMemoryAgentRunner(),
2013
});
@@ -32,7 +25,7 @@ app.use(
3225
exposeHeaders: ["Content-Type"],
3326
credentials: true,
3427
maxAge: 86400,
35-
})
28+
}),
3629
);
3730

3831
// Create the CopilotKit endpoint
@@ -46,6 +39,4 @@ app.route("/", copilotApp);
4639

4740
const port = Number(process.env.PORT || 3001);
4841
serve({ fetch: app.fetch, port });
49-
console.log(
50-
`CopilotKit runtime listening at http://localhost:${port}/api/copilotkit`
51-
);
42+
console.log(`CopilotKit runtime listening at http://localhost:${port}/api/copilotkit`);

apps/angular/demo/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"@copilotkitnext/angular": "workspace:*",
2323
"rxjs": "^7.8.1",
2424
"tslib": "^2.8.1",
25+
"zod": "^3.25.75",
2526
"zone.js": "^0.14.0"
2627
},
2728
"devDependencies": {
Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { ApplicationConfig, importProvidersFrom } from "@angular/core";
22
import { BrowserModule } from "@angular/platform-browser";
3-
import {
4-
provideCopilotKit,
5-
provideCopilotChatLabels,
6-
} from "@copilotkitnext/angular";
3+
import { provideCopilotKit, provideCopilotChatLabels } from "@copilotkitnext/angular";
74
import { WildcardToolRenderComponent } from "./components/wildcard-tool-render.component";
5+
import { helloWorldToolConfig } from "./tools/hello-world";
86

97
export const appConfig: ApplicationConfig = {
108
providers: [
@@ -17,13 +15,12 @@ export const appConfig: ApplicationConfig = {
1715
component: WildcardToolRenderComponent,
1816
} as any,
1917
],
20-
frontendTools: [],
18+
frontendTools: [helloWorldToolConfig],
2119
humanInTheLoop: [],
2220
}),
2321
provideCopilotChatLabels({
2422
chatInputPlaceholder: "Ask me anything...",
25-
chatDisclaimerText:
26-
"CopilotKit Angular Demo - AI responses may need verification.",
23+
chatDisclaimerText: "CopilotKit Angular Demo - AI responses may need verification.",
2724
}),
2825
],
2926
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Component, input } from "@angular/core";
2+
import { FrontendToolConfig, ToolRenderer } from "@copilotkitnext/angular";
3+
import { JsonPipe } from "@angular/common";
4+
import { AngularToolCall } from "@copilotkitnext/angular";
5+
import { z } from "zod";
6+
7+
@Component({
8+
selector: "app-hello-world-tool",
9+
template: ` <pre>{{ toolCall() | json }}</pre> `,
10+
standalone: true,
11+
imports: [JsonPipe],
12+
})
13+
export class HelloWorldTool implements ToolRenderer {
14+
toolCall = input.required<AngularToolCall>();
15+
}
16+
17+
export const helloWorldToolConfig: FrontendToolConfig = {
18+
name: "hello_world",
19+
description: "Says hello to the world",
20+
args: z.object({
21+
name: z.string(),
22+
}),
23+
component: HelloWorldTool,
24+
handler: async (args) => {
25+
return `Hello ${args.name}!`;
26+
},
27+
};

apps/react/demo/src/app/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ function Chat() {
8787

8888
const greeting = "Hello Copilot! 👋 Could you help me with something?";
8989

90-
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value")?.set;
90+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
91+
window.HTMLTextAreaElement.prototype,
92+
"value",
93+
)?.set;
9194
nativeInputValueSetter?.call(textarea, greeting);
9295
textarea.dispatchEvent(new Event("input", { bubbles: true }));
9396
textarea.focus();

packages/angular/src/lib/agent.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
1-
import {
2-
DestroyRef,
3-
Injectable,
4-
inject,
5-
signal,
6-
computed,
7-
Signal,
8-
} from "@angular/core";
1+
import { DestroyRef, Injectable, inject, signal, computed, Signal } from "@angular/core";
92
import { CopilotKit } from "./copilotkit";
103
import type { AbstractAgent } from "@ag-ui/client";
114
import type { Message } from "@ag-ui/client";
@@ -61,10 +54,7 @@ export class AgentStore {
6154
export class CopilotkitAgentFactory {
6255
readonly #copilotkit = inject(CopilotKit);
6356

64-
createAgentStoreSignal(
65-
agentId: Signal<string | undefined>,
66-
destroyRef: DestroyRef
67-
): Signal<AgentStore | undefined> {
57+
createAgentStoreSignal(agentId: Signal<string | undefined>, destroyRef: DestroyRef): Signal<AgentStore | undefined> {
6858
let lastAgentStore: AgentStore | undefined;
6959

7060
return computed(() => {
@@ -75,9 +65,7 @@ export class CopilotkitAgentFactory {
7565
lastAgentStore = undefined;
7666
}
7767

78-
const abstractAgent = this.#copilotkit.getAgent(
79-
agentId() || DEFAULT_AGENT_ID
80-
);
68+
const abstractAgent = this.#copilotkit.getAgent(agentId() || DEFAULT_AGENT_ID);
8169
if (!abstractAgent) return undefined;
8270

8371
lastAgentStore = new AgentStore(abstractAgent, destroyRef);
@@ -86,13 +74,10 @@ export class CopilotkitAgentFactory {
8674
}
8775
}
8876

89-
export function injectAgentStore(
90-
agentId: string | Signal<string | undefined>
91-
): Signal<AgentStore | undefined> {
77+
export function injectAgentStore(agentId: string | Signal<string | undefined>): Signal<AgentStore | undefined> {
9278
const agentFactory = inject(CopilotkitAgentFactory);
9379
const destroyRef = inject(DestroyRef);
94-
const agentIdSignal =
95-
typeof agentId === "function" ? agentId : computed(() => agentId);
80+
const agentIdSignal = typeof agentId === "function" ? agentId : computed(() => agentId);
9681

9782
return agentFactory.createAgentStoreSignal(agentIdSignal, destroyRef);
9883
}

packages/angular/src/lib/copilotkit.ts

Lines changed: 23 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,7 @@
11
import { AbstractAgent } from "@ag-ui/client";
22
import { FrontendTool, CopilotKitCore } from "@copilotkitnext/core";
3-
import {
4-
Injectable,
5-
Injector,
6-
Signal,
7-
WritableSignal,
8-
runInInjectionContext,
9-
signal,
10-
inject,
11-
} from "@angular/core";
12-
import {
13-
FrontendToolConfig,
14-
HumanInTheLoopConfig,
15-
RenderToolCallConfig,
16-
} from "./tools";
3+
import { Injectable, Injector, Signal, WritableSignal, runInInjectionContext, signal, inject } from "@angular/core";
4+
import { FrontendToolConfig, HumanInTheLoopConfig, RenderToolCallConfig } from "./tools";
175
import { injectCopilotKitConfig } from "./config";
186
import { HumanInTheLoop } from "./human-in-the-loop";
197

@@ -22,9 +10,7 @@ export class CopilotKit {
2210
readonly #config = injectCopilotKitConfig();
2311
readonly #hitl = inject(HumanInTheLoop);
2412
readonly #rootInjector = inject(Injector);
25-
readonly #agents = signal<Record<string, AbstractAgent>>(
26-
this.#config.agents ?? {}
27-
);
13+
readonly #agents = signal<Record<string, AbstractAgent>>(this.#config.agents ?? {});
2814
readonly agents = this.#agents.asReadonly();
2915

3016
readonly core = new CopilotKitCore({
@@ -35,18 +21,12 @@ export class CopilotKit {
3521
tools: this.#config.tools,
3622
});
3723

38-
readonly #toolCallRenderConfigs: WritableSignal<RenderToolCallConfig[]> =
39-
signal([]);
40-
readonly #clientToolCallRenderConfigs: WritableSignal<FrontendToolConfig[]> =
41-
signal([]);
42-
readonly #humanInTheLoopToolRenderConfigs: WritableSignal<
43-
HumanInTheLoopConfig[]
44-
> = signal([]);
45-
46-
readonly toolCallRenderConfigs: Signal<RenderToolCallConfig[]> =
47-
this.#toolCallRenderConfigs.asReadonly();
48-
readonly clientToolCallRenderConfigs: Signal<FrontendToolConfig[]> =
49-
this.#clientToolCallRenderConfigs.asReadonly();
24+
readonly #toolCallRenderConfigs: WritableSignal<RenderToolCallConfig[]> = signal([]);
25+
readonly #clientToolCallRenderConfigs: WritableSignal<FrontendToolConfig[]> = signal([]);
26+
readonly #humanInTheLoopToolRenderConfigs: WritableSignal<HumanInTheLoopConfig[]> = signal([]);
27+
28+
readonly toolCallRenderConfigs: Signal<RenderToolCallConfig[]> = this.#toolCallRenderConfigs.asReadonly();
29+
readonly clientToolCallRenderConfigs: Signal<FrontendToolConfig[]> = this.#clientToolCallRenderConfigs.asReadonly();
5030
readonly humanInTheLoopToolRenderConfigs: Signal<HumanInTheLoopConfig[]> =
5131
this.#humanInTheLoopToolRenderConfigs.asReadonly();
5232

@@ -84,38 +64,36 @@ export class CopilotKit {
8464
#bindClientTool(
8565
clientToolWithInjector: FrontendToolConfig & {
8666
injector: Injector;
87-
}
67+
},
8868
): FrontendTool {
89-
const { injector, handler, ...frontendCandidate } = clientToolWithInjector;
69+
const { injector, handler, args, description, name, agentId } = clientToolWithInjector;
9070

9171
return {
92-
...frontendCandidate,
72+
description,
73+
name,
74+
agentId,
9375
handler: (args) => runInInjectionContext(injector, () => handler(args)),
76+
parameters: args,
9477
};
9578
}
9679

9780
addFrontendTool(
9881
clientToolWithInjector: FrontendToolConfig & {
9982
injector: Injector;
100-
}
83+
},
10184
): void {
10285
const tool = this.#bindClientTool(clientToolWithInjector);
10386

10487
this.core.addTool(tool);
10588

106-
this.#clientToolCallRenderConfigs.update((current) => [
107-
...current,
108-
clientToolWithInjector,
109-
]);
89+
this.#clientToolCallRenderConfigs.update((current) => [...current, clientToolWithInjector]);
11090
}
11191

11292
addRenderToolCall(renderConfig: RenderToolCallConfig): void {
11393
this.#toolCallRenderConfigs.update((current) => [...current, renderConfig]);
11494
}
11595

116-
#bindHumanInTheLoopTool(
117-
humanInTheLoopTool: HumanInTheLoopConfig
118-
): FrontendTool {
96+
#bindHumanInTheLoopTool(humanInTheLoopTool: HumanInTheLoopConfig): FrontendTool {
11997
return {
12098
...humanInTheLoopTool,
12199
handler: (args, toolCall) => {
@@ -125,20 +103,14 @@ export class CopilotKit {
125103
}
126104

127105
addHumanInTheLoop(humanInTheLoopTool: HumanInTheLoopConfig): void {
128-
this.#humanInTheLoopToolRenderConfigs.update((current) => [
129-
...current,
130-
humanInTheLoopTool,
131-
]);
106+
this.#humanInTheLoopToolRenderConfigs.update((current) => [...current, humanInTheLoopTool]);
132107

133108
const tool = this.#bindHumanInTheLoopTool(humanInTheLoopTool);
134109

135110
this.core.addTool(tool);
136111
}
137112

138-
#isSameAgentId<T extends { agentId?: string }>(
139-
target: T,
140-
agentId?: string
141-
): boolean {
113+
#isSameAgentId<T extends { agentId?: string }>(target: T, agentId?: string): boolean {
142114
if (agentId) {
143115
return target.agentId === agentId;
144116
}
@@ -149,25 +121,13 @@ export class CopilotKit {
149121
removeTool(toolName: string, agentId?: string): void {
150122
this.core.removeTool(toolName);
151123
this.#clientToolCallRenderConfigs.update((current) =>
152-
current.filter(
153-
(renderConfig) =>
154-
renderConfig.name !== toolName &&
155-
this.#isSameAgentId(renderConfig, agentId)
156-
)
124+
current.filter((renderConfig) => renderConfig.name !== toolName && this.#isSameAgentId(renderConfig, agentId)),
157125
);
158126
this.#humanInTheLoopToolRenderConfigs.update((current) =>
159-
current.filter(
160-
(renderConfig) =>
161-
renderConfig.name !== toolName &&
162-
this.#isSameAgentId(renderConfig, agentId)
163-
)
127+
current.filter((renderConfig) => renderConfig.name !== toolName && this.#isSameAgentId(renderConfig, agentId)),
164128
);
165129
this.#toolCallRenderConfigs.update((current) =>
166-
current.filter(
167-
(renderConfig) =>
168-
renderConfig.name !== toolName &&
169-
this.#isSameAgentId(renderConfig, agentId)
170-
)
130+
current.filter((renderConfig) => renderConfig.name !== toolName && this.#isSameAgentId(renderConfig, agentId)),
171131
);
172132
}
173133

0 commit comments

Comments
 (0)