Skip to content

Commit 03e34bd

Browse files
committed
feat(core): add context provider plugin system (AIPexStudio#54)
1 parent cd9866a commit 03e34bd

File tree

19 files changed

+1803
-304
lines changed

19 files changed

+1803
-304
lines changed

packages/browser-ext/src/components/chatbot/__tests__/use-chat.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,10 @@ describe("useChat", () => {
179179
await result.current.sendMessage("Hi there");
180180
});
181181

182-
expect(agent.chat).toHaveBeenCalledWith("Hi there", undefined);
182+
expect(agent.chat).toHaveBeenCalledWith("Hi there", {
183+
sessionId: undefined,
184+
contexts: undefined,
185+
});
183186
expect(result.current.sessionId).toBe("session-1");
184187
expect(result.current.messages[0].role).toBe("user");
185188
});

packages/browser-ext/src/hooks/use-agent.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import {
22
AIPex,
33
aisdk,
4+
ContextManager,
45
IndexedDBStorage,
56
SessionStorage,
67
} from "@aipexstudio/aipex-core";
78
import { useEffect, useMemo, useState } from "react";
89
import { SYSTEM_PROMPT } from "~/components/chatbot/constants";
910
import { createAIProvider } from "~/lib/ai-provider";
11+
import { allBrowserProviders } from "~/lib/context/providers";
1012
import { allBrowserTools } from "~/tools";
1113
import type { ChatSettings } from "~/types";
1214

@@ -64,6 +66,12 @@ export function useAgent({
6466
}),
6567
);
6668

69+
// Create context manager with browser providers
70+
const contextManager = new ContextManager({
71+
providers: allBrowserProviders,
72+
autoInitialize: true,
73+
});
74+
6775
// Get all available tools
6876
const tools = [...allBrowserTools];
6977

@@ -74,6 +82,7 @@ export function useAgent({
7482
model,
7583
tools,
7684
storage,
85+
contextManager,
7786
maxTurns: 10,
7887
});
7988

packages/browser-ext/src/hooks/use-chat.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { AgentEvent, AIPex } from "@aipexstudio/aipex-core";
1+
import type { AgentEvent, AIPex, Context } from "@aipexstudio/aipex-core";
22
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
33
import { ChatAdapter } from "../adapters/chat-adapter";
44
import type { ChatConfig, ChatStatus, ContextItem, UIMessage } from "../types";
@@ -190,19 +190,21 @@ export function useChat(
190190
handlersRef.current?.onMessageSent?.(userMessage);
191191
adapter.setStatus("submitted");
192192

193-
// Build the input text with context
194-
let inputText = text;
195-
if (contexts && contexts.length > 0) {
196-
const contextText = contexts
197-
.map((ctx) => `[${ctx.type}: ${ctx.label}]\n${ctx.value}`)
198-
.join("\n\n");
199-
inputText = `${contextText}\n\n${text}`;
200-
}
201-
202-
const events = agent.chat(
203-
inputText,
204-
sessionId ? { sessionId } : undefined,
205-
);
193+
// Convert ContextItem to core Context type
194+
const coreContexts: Context[] | undefined = contexts?.map((ctx) => ({
195+
id: ctx.id,
196+
type: ctx.type as Context["type"],
197+
providerId: "ui-selected",
198+
label: ctx.label,
199+
value: ctx.value,
200+
metadata: ctx.metadata,
201+
timestamp: Date.now(),
202+
}));
203+
204+
const events = agent.chat(text, {
205+
sessionId: sessionId ?? undefined,
206+
contexts: coreContexts,
207+
});
206208
await processAgentEvents(events);
207209
},
208210
[adapter, agent, sessionId, processAgentEvents],

packages/browser-ext/src/hooks/use-tabs-sync.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,78 @@
33
* Monitors tab changes and updates available contexts accordingly
44
*/
55

6+
import type { Context } from "@aipexstudio/aipex-core";
67
import { useCallback, useEffect, useRef } from "react";
78
import type { ContextItem } from "@/components/ai-elements/prompt-input";
8-
import { getAllAvailableContexts } from "~/lib/context-providers";
9+
import {
10+
BookmarksProvider,
11+
CurrentPageProvider,
12+
TabsProvider,
13+
} from "~/lib/context/providers";
14+
15+
const currentPageProvider = new CurrentPageProvider();
16+
const tabsProvider = new TabsProvider();
17+
const bookmarksProvider = new BookmarksProvider();
18+
19+
/**
20+
* Get all available contexts from providers
21+
* Converts core Context to UI ContextItem
22+
*/
23+
async function getAllAvailableContexts(): Promise<ContextItem[]> {
24+
const results = await Promise.allSettled([
25+
currentPageProvider.getContexts(),
26+
tabsProvider.getContexts(),
27+
bookmarksProvider.getContexts(),
28+
]);
29+
30+
const contexts: Context[] = [];
31+
let currentPageTabId: number | null = null;
32+
let currentPageUrl: string | null = null;
33+
34+
// Current page - add first and record its tab ID and URL
35+
if (results[0].status === "fulfilled" && results[0].value.length > 0) {
36+
const currentPage = results[0].value[0];
37+
contexts.push(currentPage);
38+
39+
// Extract tab ID from metadata
40+
currentPageTabId = currentPage.metadata?.tabId as number | null;
41+
currentPageUrl = currentPage.metadata?.url as string | null;
42+
}
43+
44+
// Tabs - exclude the current page tab
45+
if (results[1].status === "fulfilled") {
46+
const allTabs = results[1].value;
47+
const filteredTabs = allTabs.filter((tab) => {
48+
const tabId = tab.metadata?.tabId as number | undefined;
49+
if (currentPageTabId !== null && tabId === currentPageTabId) {
50+
return false;
51+
}
52+
return true;
53+
});
54+
contexts.push(...filteredTabs);
55+
}
56+
57+
// Bookmarks - exclude if URL matches current page
58+
if (results[2].status === "fulfilled") {
59+
const allBookmarks = results[2].value;
60+
const filteredBookmarks = allBookmarks.filter((bookmark) => {
61+
if (currentPageUrl && bookmark.metadata?.url === currentPageUrl) {
62+
return false;
63+
}
64+
return true;
65+
});
66+
contexts.push(...filteredBookmarks);
67+
}
68+
69+
// Convert core Context to UI ContextItem
70+
return contexts.map((ctx) => ({
71+
id: ctx.id,
72+
type: ctx.type as ContextItem["type"],
73+
label: ctx.label,
74+
value: typeof ctx.value === "string" ? ctx.value : "",
75+
metadata: ctx.metadata,
76+
}));
77+
}
978

1079
interface UseTabsSyncOptions {
1180
/**

0 commit comments

Comments
 (0)