Skip to content

Commit 13225ea

Browse files
committed
chore: remove unsafe assertions and tighten tooling types
1 parent c9001c1 commit 13225ea

File tree

14 files changed

+98
-136
lines changed

14 files changed

+98
-136
lines changed

packages/aipex-react/src/hooks/use-agent.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import {
99
AIPex,
1010
ContextManager,
1111
type ContextProvider,
12+
type FunctionTool,
1213
type SessionStorageAdapter,
13-
type UnifiedToolDefinition,
1414
} from "@aipexstudio/aipex-core";
1515
import { useEffect, useRef, useState } from "react";
1616
import type { ChatSettings } from "../types";
@@ -32,7 +32,7 @@ export interface UseAgentOptions {
3232
contextProviders?: ContextProvider[];
3333

3434
/** Tools to register with the agent (optional) */
35-
tools?: UnifiedToolDefinition[];
35+
tools?: FunctionTool[];
3636

3737
/** System instructions (optional) */
3838
instructions?: string;
@@ -162,8 +162,7 @@ export function useAgent({
162162
name,
163163
instructions: instructions ?? "You are a helpful AI assistant.",
164164
model,
165-
tools:
166-
toolsRef.current.length > 0 ? (toolsRef.current as any) : undefined,
165+
tools: toolsRef.current.length > 0 ? toolsRef.current : undefined,
167166
storage: storageRef.current,
168167
contextManager,
169168
maxTurns,

packages/aipex-react/src/i18n/tool-names.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ export const translatedToolName = (
1010
toolName: string,
1111
): string => {
1212
try {
13+
const translationKey = `tools.${toolName}` as const;
1314
// Try to get translation from tools namespace
14-
const translatedName = t(`tools.${toolName}` as any);
15+
const translatedName = t(translationKey, {
16+
defaultValue: translationKey,
17+
});
1518

1619
// If the translated name is the same as the key, it means no translation was found
17-
if (translatedName === `tools.${toolName}`) {
20+
if (translatedName === translationKey) {
1821
// Return formatted original name as fallback
1922
return formatToolName(toolName);
2023
}

packages/aipex-react/src/i18n/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export interface TranslationResources {
104104
};
105105
}
106106

107-
export type TranslationKey =
107+
export type BaseTranslationKey =
108108
| "common.title"
109109
| "common.settings"
110110
| "common.newChat"
@@ -186,6 +186,8 @@ export type TranslationKey =
186186
| "config.apiTokenRequired"
187187
| "config.openSettings";
188188

189+
export type TranslationKey = BaseTranslationKey | `tools.${string}`;
190+
189191
export interface I18nContextValue {
190192
language: Language;
191193
t: (key: TranslationKey, params?: Record<string, string | number>) => string;

packages/aipex-react/src/lib/storage.ts

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
* Provides a simple browser localStorage backend for settings and data persistence
44
*/
55

6-
import type { KeyValueStorage, WatchCallback } from "@aipexstudio/aipex-core";
6+
import {
7+
type KeyValueStorage,
8+
safeJsonParse,
9+
type WatchCallback,
10+
} from "@aipexstudio/aipex-core";
711

812
export class LocalStorageKeyValueAdapter<T = unknown>
913
implements KeyValueStorage<T>
@@ -22,24 +26,8 @@ export class LocalStorageKeyValueAdapter<T = unknown>
2226
if (event.key && this.watchers.has(event.key)) {
2327
const callbacks = this.watchers.get(event.key);
2428
if (callbacks) {
25-
let newValue: T | undefined;
26-
let oldValue: T | undefined;
27-
28-
try {
29-
newValue = event.newValue
30-
? (JSON.parse(event.newValue) as T)
31-
: undefined;
32-
} catch {
33-
newValue = undefined;
34-
}
35-
36-
try {
37-
oldValue = event.oldValue
38-
? (JSON.parse(event.oldValue) as T)
39-
: undefined;
40-
} catch {
41-
oldValue = undefined;
42-
}
29+
const newValue = safeJsonParse<T>(event.newValue);
30+
const oldValue = safeJsonParse<T>(event.oldValue);
4331

4432
for (const callback of callbacks) {
4533
callback({ newValue, oldValue });
@@ -76,8 +64,8 @@ export class LocalStorageKeyValueAdapter<T = unknown>
7664
async load(key: string): Promise<T | null> {
7765
if (typeof localStorage === "undefined") return null;
7866
try {
79-
const value = localStorage.getItem(key);
80-
return value ? (JSON.parse(value) as T) : null;
67+
const value = safeJsonParse<T>(localStorage.getItem(key));
68+
return value ?? null;
8169
} catch {
8270
return null;
8371
}
@@ -99,9 +87,9 @@ export class LocalStorageKeyValueAdapter<T = unknown>
9987
const key = localStorage.key(i);
10088
if (key) {
10189
try {
102-
const value = localStorage.getItem(key);
103-
if (value) {
104-
results.push(JSON.parse(value) as T);
90+
const value = safeJsonParse<T>(localStorage.getItem(key));
91+
if (value !== undefined) {
92+
results.push(value);
10593
}
10694
} catch {
10795
// Skip non-JSON values

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* configuration (browser tools, context providers, storage).
66
*/
77

8+
import type { FunctionTool } from "@aipexstudio/aipex-core";
89
import { aisdk, SessionStorage } from "@aipexstudio/aipex-core";
910
import {
1011
createAIProvider,
@@ -67,7 +68,7 @@ export function useAgent({
6768

6869
// Stable references for context providers and tools to prevent infinite loops
6970
const contextProvidersRef = useRef(allBrowserProviders);
70-
const toolsRef = useRef(allBrowserTools);
71+
const toolsRef = useRef<FunctionTool[]>(allBrowserTools);
7172

7273
// Use the generic hook with browser-specific configuration
7374
return useAgentCore({
@@ -76,7 +77,7 @@ export function useAgent({
7677
modelFactory: modelFactoryRef.current,
7778
storage,
7879
contextProviders: contextProvidersRef.current,
79-
tools: toolsRef.current as any,
80+
tools: toolsRef.current,
8081
instructions: SYSTEM_PROMPT,
8182
name: "AIPex Browser Assistant",
8283
maxTurns: 10,

packages/browser-ext/vite.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import path from "node:path";
2-
import { crx } from "@crxjs/vite-plugin";
2+
import { crx, type ManifestV3Export } from "@crxjs/vite-plugin";
33
import react from "@vitejs/plugin-react";
44
import { defineConfig } from "vite";
55
import { viteStaticCopy } from "vite-plugin-static-copy";
@@ -9,7 +9,7 @@ import manifest from "./manifest.json";
99
export default defineConfig({
1010
plugins: [
1111
react(),
12-
crx({ manifest: manifest as any }),
12+
crx({ manifest: manifest as unknown as ManifestV3Export }),
1313
viteStaticCopy({
1414
targets: [
1515
{

packages/browser-runtime/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"type": "module",
2323
"dependencies": {
2424
"@aipexstudio/aipex-core": "workspace:*",
25+
"micromatch": "^4.0.8",
2526
"nanoid": "^5.1.6",
2627
"zod": "^4.1.13"
2728
},
@@ -35,6 +36,7 @@
3536
}
3637
},
3738
"devDependencies": {
39+
"@types/micromatch": "^4.0.10",
3840
"@types/react": "19.2.7"
3941
}
4042
}

packages/browser-runtime/src/automation/query.ts

Lines changed: 17 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
/**
2-
* Snapshot Query and Search System
3-
*
4-
* Provides search functionality for snapshot text with glob pattern support
5-
*/
1+
import { matcher } from "micromatch";
62

73
export const SKIP_ROLES = [
84
"generic",
@@ -21,59 +17,7 @@ export const SKIP_ROLES = [
2117
];
2218

2319
function hasGlobPattern(str: string): boolean {
24-
return /[*?[\]{}]/.test(str);
25-
}
26-
27-
function matchGlob(
28-
pattern: string,
29-
text: string,
30-
caseSensitive: boolean = false,
31-
): boolean {
32-
if (!caseSensitive) {
33-
pattern = pattern.toLowerCase();
34-
text = text.toLowerCase();
35-
}
36-
37-
if (pattern.includes("{") && pattern.includes("}")) {
38-
const braceStart = pattern.indexOf("{");
39-
const braceEnd = pattern.indexOf("}");
40-
if (braceStart < braceEnd) {
41-
const prefix = pattern.substring(0, braceStart);
42-
const suffix = pattern.substring(braceEnd + 1);
43-
const alternatives = pattern
44-
.substring(braceStart + 1, braceEnd)
45-
.split(",");
46-
47-
for (const alt of alternatives) {
48-
const fullPattern = prefix + alt.trim() + suffix;
49-
if (matchGlob(fullPattern, text, caseSensitive)) {
50-
return true;
51-
}
52-
}
53-
return false;
54-
}
55-
}
56-
57-
let regexPattern = pattern
58-
.replace(/[.*+^${}()|[\]\\]/g, "\\$&")
59-
.replace(/\\\*/g, ".*")
60-
.replace(/\\\?/g, ".")
61-
.replace(/\\\[/g, "[")
62-
.replace(/\\\]/g, "]");
63-
64-
regexPattern = regexPattern.replace(/\[([^\]]+)\]/g, (_, chars) => {
65-
if (chars.includes("-") && chars.length === 3) {
66-
return `[${chars}]`;
67-
}
68-
return `[${chars.replace(/[.*+^${}()|[\]\\]/g, "\\$&")}]`;
69-
});
70-
71-
try {
72-
const regex = new RegExp(`${regexPattern}`, "i");
73-
return regex.test(text);
74-
} catch {
75-
return false;
76-
}
20+
return /[*?[{\]}]/.test(str);
7721
}
7822

7923
export interface SearchOptions {
@@ -108,6 +52,9 @@ export function searchSnapshotText(
10852
useGlob !== undefined
10953
? useGlob
11054
: searchTerms.some((term) => hasGlobPattern(term));
55+
const matcherFns = shouldUseGlob
56+
? searchTerms.map((term) => matcher(term, { nocase: !caseSensitive }))
57+
: [];
11158

11259
const lines = snapshotText.split("\n");
11360
const matchedLines: number[] = [];
@@ -117,7 +64,9 @@ export function searchSnapshotText(
11764
if (line === undefined) {
11865
continue;
11966
}
120-
if (matchLine(line, searchTerms, caseSensitive, shouldUseGlob)) {
67+
if (
68+
matchLine(line, searchTerms, matcherFns, caseSensitive, shouldUseGlob)
69+
) {
12170
matchedLines.push(i);
12271
}
12372
}
@@ -134,23 +83,19 @@ export function searchSnapshotText(
13483
function matchLine(
13584
line: string,
13685
searchTerms: string[],
86+
matchers: Array<(value: string) => boolean>,
13787
caseSensitive: boolean,
13888
useGlob: boolean,
13989
): boolean {
140-
for (const term of searchTerms) {
141-
if (useGlob) {
142-
if (matchGlob(term, line, caseSensitive)) {
143-
return true;
144-
}
145-
} else {
146-
const lineValue = caseSensitive ? line : line.toLowerCase();
147-
const searchTerm = caseSensitive ? term : term.toLowerCase();
148-
if (lineValue.includes(searchTerm)) {
149-
return true;
150-
}
151-
}
90+
if (useGlob) {
91+
return matchers.some((match) => match(line));
15292
}
153-
return false;
93+
94+
const lineValue = caseSensitive ? line : line.toLowerCase();
95+
return searchTerms.some((term) => {
96+
const searchTerm = caseSensitive ? term : term.toLowerCase();
97+
return lineValue.includes(searchTerm);
98+
});
15499
}
155100

156101
function expandLineContext(

packages/browser-runtime/src/storage/storage-adapter.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { KeyValueStorage } from "@aipexstudio/aipex-core";
1+
import { type KeyValueStorage, safeJsonParse } from "@aipexstudio/aipex-core";
22

33
export type WatchCallback<T> = (change: { newValue?: T; oldValue?: T }) => void;
44

@@ -40,12 +40,8 @@ export class ChromeStorageAdapter<T = unknown> implements KeyValueStorage<T> {
4040
const result = await area.get(key);
4141
return (result[key] as T) ?? null;
4242
}
43-
try {
44-
const value = localStorage.getItem(key);
45-
return value ? (JSON.parse(value) as T) : null;
46-
} catch {
47-
return null;
48-
}
43+
const parsed = safeJsonParse<T>(localStorage.getItem(key));
44+
return parsed ?? null;
4945
}
5046

5147
async delete(key: string): Promise<void> {
@@ -72,9 +68,9 @@ export class ChromeStorageAdapter<T = unknown> implements KeyValueStorage<T> {
7268
const key = localStorage.key(i);
7369
if (key) {
7470
try {
75-
const value = localStorage.getItem(key);
76-
if (value) {
77-
values.push(JSON.parse(value) as T);
71+
const value = safeJsonParse<T>(localStorage.getItem(key));
72+
if (value !== undefined) {
73+
values.push(value);
7874
}
7975
} catch {
8076
// Skip non-JSON values
@@ -139,9 +135,9 @@ export class ChromeStorageAdapter<T = unknown> implements KeyValueStorage<T> {
139135
const key = localStorage.key(i);
140136
if (key) {
141137
try {
142-
const value = localStorage.getItem(key);
143-
if (value) {
144-
result[key] = JSON.parse(value) as T;
138+
const value = safeJsonParse<T>(localStorage.getItem(key));
139+
if (value !== undefined) {
140+
result[key] = value;
145141
}
146142
} catch {
147143
// Skip non-JSON values

packages/browser-runtime/src/tools/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { FunctionTool } from "@aipexstudio/aipex-core";
2+
13
// Re-export all tool modules
24
export * from "./bookmark";
35
export * from "./element";
@@ -55,7 +57,7 @@ import {
5557
switchToTabTool,
5658
} from "./tab";
5759

58-
export const allBrowserTools = [
60+
export const allBrowserTools: FunctionTool[] = [
5961
// Page tools
6062
getPageInfoTool,
6163
scrollPageTool,

0 commit comments

Comments
 (0)