@aliou/pi-synthetic 0.8.5 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -7
- package/package.json +2 -2
- package/src/extensions/command-quotas/command.ts +16 -4
- package/src/extensions/command-quotas/index.ts +0 -5
- package/src/extensions/command-quotas/sub-integration.ts +42 -20
- package/src/extensions/provider/models.test.ts +1 -1
- package/src/extensions/provider/models.ts +1 -1
- package/src/extensions/web-search/index.ts +0 -7
- package/src/extensions/web-search/tool.ts +6 -3
- package/src/lib/env.ts +16 -5
- package/src/utils/quotas.ts +7 -6
- package/src/extensions/web-search/hooks.ts +0 -104
package/README.md
CHANGED
|
@@ -8,18 +8,27 @@ A Pi extension that adds [Synthetic](https://synthetic.new) as a model provider,
|
|
|
8
8
|
|
|
9
9
|
Sign up at [synthetic.new](https://synthetic.new/?referral=NDWw1u3UDWiFyDR) to get an API key (referral link).
|
|
10
10
|
|
|
11
|
-
###
|
|
11
|
+
### Configure Credentials
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
The extension uses Pi's credential storage. Add your API key to `~/.pi/agent/auth.json` (recommended):
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"synthetic": { "type": "api_key", "key": "your-api-key-here" }
|
|
18
|
+
}
|
|
15
19
|
```
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
Or set environment variable:
|
|
18
22
|
|
|
19
23
|
```bash
|
|
20
|
-
|
|
24
|
+
export SYNTHETIC_API_KEY="your-api-key-here"
|
|
21
25
|
```
|
|
22
26
|
|
|
27
|
+
Credentials are resolved in this order:
|
|
28
|
+
1. CLI `--api-key` flag
|
|
29
|
+
2. `auth.json` entry for `synthetic`
|
|
30
|
+
3. Environment variable `SYNTHETIC_API_KEY`
|
|
31
|
+
|
|
23
32
|
### Install Extension
|
|
24
33
|
|
|
25
34
|
```bash
|
|
@@ -43,7 +52,7 @@ Once installed, select `synthetic` as your provider and choose from available mo
|
|
|
43
52
|
|
|
44
53
|
### Web Search Tool
|
|
45
54
|
|
|
46
|
-
The extension registers `synthetic_web_search` — a zero-data-retention web search tool.
|
|
55
|
+
The extension registers `synthetic_web_search` — a zero-data-retention web search tool. The tool is always visible; it fails with a clear message if credentials are missing or the account lacks a subscription.
|
|
47
56
|
|
|
48
57
|
### Reasoning Levels
|
|
49
58
|
|
|
@@ -124,7 +133,7 @@ This repository uses [Changesets](https://github.com/changesets/changesets) for
|
|
|
124
133
|
## Requirements
|
|
125
134
|
|
|
126
135
|
- Pi coding agent v0.50.0+
|
|
127
|
-
-
|
|
136
|
+
- Synthetic API key (configured in `~/.pi/agent/auth.json` or via `SYNTHETIC_API_KEY`)
|
|
128
137
|
|
|
129
138
|
## Links
|
|
130
139
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aliou/pi-synthetic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@aliou/pi-utils-ui": "^0.1.2"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@aliou/biome-plugins": "^0.
|
|
39
|
+
"@aliou/biome-plugins": "^0.7.0",
|
|
40
40
|
"@biomejs/biome": "^2.4.2",
|
|
41
41
|
"@changesets/cli": "^2.27.11",
|
|
42
42
|
"@mariozechner/pi-coding-agent": "0.61.0",
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { getSyntheticApiKey } from "../../lib/env";
|
|
2
3
|
import { fetchQuotas } from "../../utils/quotas";
|
|
3
4
|
import { QuotasComponent } from "./components/quotas-display";
|
|
4
5
|
|
|
6
|
+
const MISSING_AUTH_MESSAGE =
|
|
7
|
+
"Synthetic quotas requires a Synthetic subscription. Add credentials to ~/.pi/agent/auth.json or set SYNTHETIC_API_KEY environment variable.";
|
|
8
|
+
|
|
5
9
|
export function registerQuotasCommand(pi: ExtensionAPI): void {
|
|
6
10
|
pi.registerCommand("synthetic:quotas", {
|
|
7
11
|
description: "Display Synthetic API usage quotas",
|
|
8
12
|
handler: async (_args, ctx) => {
|
|
13
|
+
const apiKey = await getSyntheticApiKey(ctx.modelRegistry.authStorage);
|
|
14
|
+
if (!apiKey) {
|
|
15
|
+
ctx.ui.notify(MISSING_AUTH_MESSAGE, "warning");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
const result = await ctx.ui.custom<null>((tui, theme, _kb, done) => {
|
|
10
20
|
const component = new QuotasComponent(theme, () => done(null));
|
|
11
21
|
|
|
12
|
-
fetchQuotas()
|
|
22
|
+
fetchQuotas(apiKey)
|
|
13
23
|
.then((quotas) => {
|
|
14
24
|
if (!quotas) {
|
|
15
25
|
component.setState({
|
|
16
26
|
type: "error",
|
|
17
|
-
message:
|
|
27
|
+
message:
|
|
28
|
+
"Failed to fetch quotas. Check your Synthetic subscription status.",
|
|
18
29
|
});
|
|
19
30
|
} else {
|
|
20
31
|
component.setState({ type: "loaded", quotas });
|
|
@@ -24,7 +35,8 @@ export function registerQuotasCommand(pi: ExtensionAPI): void {
|
|
|
24
35
|
.catch(() => {
|
|
25
36
|
component.setState({
|
|
26
37
|
type: "error",
|
|
27
|
-
message:
|
|
38
|
+
message:
|
|
39
|
+
"Failed to fetch quotas. Check your Synthetic subscription status.",
|
|
28
40
|
});
|
|
29
41
|
tui.requestRender();
|
|
30
42
|
});
|
|
@@ -38,7 +50,7 @@ export function registerQuotasCommand(pi: ExtensionAPI): void {
|
|
|
38
50
|
|
|
39
51
|
// RPC fallback: return JSON
|
|
40
52
|
if (result === undefined) {
|
|
41
|
-
const quotas = await fetchQuotas();
|
|
53
|
+
const quotas = await fetchQuotas(apiKey);
|
|
42
54
|
if (!quotas) {
|
|
43
55
|
ctx.ui.notify(
|
|
44
56
|
JSON.stringify({ error: "Failed to fetch quotas" }),
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { hasSyntheticApiKey } from "../../lib/env";
|
|
3
2
|
import { registerQuotasCommand } from "./command";
|
|
4
3
|
import { registerSubIntegration } from "./sub-integration";
|
|
5
4
|
|
|
6
5
|
export default async function (pi: ExtensionAPI) {
|
|
7
|
-
if (!hasSyntheticApiKey()) {
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
6
|
registerQuotasCommand(pi);
|
|
12
7
|
registerSubIntegration(pi);
|
|
13
8
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
1
|
+
import type { AuthStorage, ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { getSyntheticApiKey } from "../../lib/env";
|
|
2
3
|
import type { QuotasResponse } from "../../types/quotas";
|
|
3
4
|
import { fetchQuotas, formatResetTime } from "../../utils/quotas";
|
|
4
5
|
|
|
@@ -68,8 +69,13 @@ function toUsageSnapshot(quotas: QuotasResponse): UsageSnapshot {
|
|
|
68
69
|
};
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
async function emitCurrentUsage(
|
|
72
|
-
|
|
72
|
+
async function emitCurrentUsage(
|
|
73
|
+
pi: ExtensionAPI,
|
|
74
|
+
authStorage: AuthStorage,
|
|
75
|
+
): Promise<void> {
|
|
76
|
+
const apiKey = await getSyntheticApiKey(authStorage);
|
|
77
|
+
if (!apiKey) return;
|
|
78
|
+
const quotas = await fetchQuotas(apiKey);
|
|
73
79
|
if (!quotas) return;
|
|
74
80
|
pi.events.emit("sub-core:update-current", {
|
|
75
81
|
state: { provider: "synthetic", usage: toUsageSnapshot(quotas) },
|
|
@@ -77,12 +83,11 @@ async function emitCurrentUsage(pi: ExtensionAPI): Promise<void> {
|
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
export function registerSubIntegration(pi: ExtensionAPI): void {
|
|
80
|
-
if (!process.env.SYNTHETIC_API_KEY) return;
|
|
81
|
-
|
|
82
86
|
let interval: NodeJS.Timeout | undefined;
|
|
83
87
|
let refreshMs = 60000;
|
|
84
88
|
let subCoreReady = false;
|
|
85
89
|
let currentProvider: string | undefined;
|
|
90
|
+
let currentAuthStorage: AuthStorage | undefined;
|
|
86
91
|
|
|
87
92
|
function isSynthetic(): boolean {
|
|
88
93
|
return currentProvider === "synthetic";
|
|
@@ -95,15 +100,15 @@ export function registerSubIntegration(pi: ExtensionAPI): void {
|
|
|
95
100
|
}
|
|
96
101
|
}
|
|
97
102
|
|
|
98
|
-
function
|
|
103
|
+
function startPolling(authStorage: AuthStorage): void {
|
|
99
104
|
stop();
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
emitCurrentUsage(pi);
|
|
105
|
+
currentAuthStorage = authStorage;
|
|
106
|
+
void emitCurrentUsage(pi, authStorage);
|
|
104
107
|
const ms = Math.max(10000, refreshMs);
|
|
105
108
|
interval = setInterval(() => {
|
|
106
|
-
if (isSynthetic())
|
|
109
|
+
if (isSynthetic() && currentAuthStorage) {
|
|
110
|
+
void emitCurrentUsage(pi, currentAuthStorage);
|
|
111
|
+
}
|
|
107
112
|
}, ms);
|
|
108
113
|
interval.unref?.();
|
|
109
114
|
}
|
|
@@ -111,28 +116,44 @@ export function registerSubIntegration(pi: ExtensionAPI): void {
|
|
|
111
116
|
// Custom events (inter-extension bus)
|
|
112
117
|
pi.events.on("sub-core:ready", () => {
|
|
113
118
|
subCoreReady = true;
|
|
114
|
-
|
|
119
|
+
// Polling starts in session_start/model_select when provider is synthetic
|
|
115
120
|
});
|
|
116
121
|
|
|
117
122
|
pi.events.on("sub-core:settings:updated", (data: unknown) => {
|
|
118
123
|
const payload = data as SubCoreSettingsPayload;
|
|
119
124
|
if (payload.settings?.behavior?.refreshInterval) {
|
|
120
125
|
refreshMs = payload.settings.behavior.refreshInterval * 1000;
|
|
121
|
-
|
|
126
|
+
// Restart with new interval if currently running
|
|
127
|
+
if (interval && isSynthetic() && currentAuthStorage) {
|
|
128
|
+
startPolling(currentAuthStorage);
|
|
129
|
+
}
|
|
122
130
|
}
|
|
123
131
|
});
|
|
124
132
|
|
|
125
|
-
// Lifecycle events
|
|
126
|
-
pi.on("session_start", (_event, ctx) => {
|
|
133
|
+
// Lifecycle events
|
|
134
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
127
135
|
currentProvider = ctx.model?.provider;
|
|
128
|
-
|
|
136
|
+
currentAuthStorage = ctx.modelRegistry.authStorage;
|
|
137
|
+
|
|
138
|
+
if (subCoreReady && isSynthetic()) {
|
|
139
|
+
const apiKey = await getSyntheticApiKey(currentAuthStorage);
|
|
140
|
+
if (apiKey) {
|
|
141
|
+
startPolling(currentAuthStorage);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
129
144
|
});
|
|
130
145
|
|
|
131
|
-
pi.on("model_select", (event,
|
|
146
|
+
pi.on("model_select", async (event, ctx) => {
|
|
132
147
|
currentProvider = event.model?.provider;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
148
|
+
currentAuthStorage = ctx.modelRegistry.authStorage;
|
|
149
|
+
|
|
150
|
+
if (subCoreReady && isSynthetic()) {
|
|
151
|
+
const apiKey = await getSyntheticApiKey(currentAuthStorage);
|
|
152
|
+
if (apiKey) {
|
|
153
|
+
startPolling(currentAuthStorage);
|
|
154
|
+
} else {
|
|
155
|
+
stop();
|
|
156
|
+
}
|
|
136
157
|
} else {
|
|
137
158
|
stop();
|
|
138
159
|
}
|
|
@@ -140,6 +161,7 @@ export function registerSubIntegration(pi: ExtensionAPI): void {
|
|
|
140
161
|
|
|
141
162
|
pi.on("session_shutdown", () => {
|
|
142
163
|
currentProvider = undefined;
|
|
164
|
+
currentAuthStorage = undefined;
|
|
143
165
|
stop();
|
|
144
166
|
});
|
|
145
167
|
}
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { hasSyntheticApiKey } from "../../lib/env";
|
|
3
|
-
import { registerSyntheticWebSearchHooks } from "./hooks";
|
|
4
2
|
import { registerSyntheticWebSearchTool } from "./tool";
|
|
5
3
|
|
|
6
4
|
export default async function (pi: ExtensionAPI) {
|
|
7
|
-
if (!hasSyntheticApiKey()) {
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
5
|
registerSyntheticWebSearchTool(pi);
|
|
12
|
-
registerSyntheticWebSearchHooks(pi);
|
|
13
6
|
}
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
import { getMarkdownTheme, keyHint } from "@mariozechner/pi-coding-agent";
|
|
10
10
|
import { Container, Markdown, Text } from "@mariozechner/pi-tui";
|
|
11
11
|
import { type Static, Type } from "@sinclair/typebox";
|
|
12
|
+
import { getSyntheticApiKey } from "../../lib/env";
|
|
12
13
|
|
|
13
14
|
export const SYNTHETIC_WEB_SEARCH_TOOL = "synthetic_web_search" as const;
|
|
14
15
|
|
|
@@ -51,16 +52,18 @@ export function registerSyntheticWebSearchTool(pi: ExtensionAPI): void {
|
|
|
51
52
|
onUpdate:
|
|
52
53
|
| ((result: AgentToolResult<WebSearchDetails>) => void)
|
|
53
54
|
| undefined,
|
|
54
|
-
|
|
55
|
+
ctx: ExtensionContext,
|
|
55
56
|
): Promise<AgentToolResult<WebSearchDetails>> {
|
|
56
57
|
onUpdate?.({
|
|
57
58
|
content: [{ type: "text", text: "Searching..." }],
|
|
58
59
|
details: { query: params.query },
|
|
59
60
|
});
|
|
60
61
|
|
|
61
|
-
const apiKey =
|
|
62
|
+
const apiKey = await getSyntheticApiKey(ctx.modelRegistry.authStorage);
|
|
62
63
|
if (!apiKey) {
|
|
63
|
-
throw new Error(
|
|
64
|
+
throw new Error(
|
|
65
|
+
"Synthetic web search requires a Synthetic subscription. Add credentials to ~/.pi/agent/auth.json or set SYNTHETIC_API_KEY environment variable.",
|
|
66
|
+
);
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
const response = await fetch("https://api.synthetic.new/v2/search", {
|
package/src/lib/env.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
const PROVIDER_ID = "synthetic";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Get the Synthetic API key through Pi's auth handling.
|
|
7
|
+
*
|
|
8
|
+
* Resolution order:
|
|
9
|
+
* 1. Runtime override (CLI --api-key)
|
|
10
|
+
* 2. auth.json entry for "synthetic"
|
|
11
|
+
* 3. Environment variable SYNTHETIC_API_KEY
|
|
12
|
+
*/
|
|
13
|
+
export async function getSyntheticApiKey(
|
|
14
|
+
authStorage: AuthStorage,
|
|
15
|
+
): Promise<string | undefined> {
|
|
16
|
+
const key = await authStorage.getApiKey(PROVIDER_ID);
|
|
17
|
+
return key ?? process.env.SYNTHETIC_API_KEY;
|
|
7
18
|
}
|
package/src/utils/quotas.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import type { QuotasResponse } from "../types/quotas";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if (!
|
|
3
|
+
export async function fetchQuotas(
|
|
4
|
+
apiKey: string,
|
|
5
|
+
): Promise<QuotasResponse | null> {
|
|
6
|
+
if (!apiKey) return null;
|
|
7
7
|
|
|
8
8
|
try {
|
|
9
9
|
const response = await fetch("https://api.synthetic.new/v2/quotas", {
|
|
10
|
-
headers: { Authorization: `Bearer ${
|
|
10
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
if (!response.ok) return null;
|
|
14
|
-
|
|
14
|
+
const data: QuotasResponse = await response.json();
|
|
15
|
+
return data;
|
|
15
16
|
} catch {
|
|
16
17
|
return null;
|
|
17
18
|
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { SYNTHETIC_WEB_SEARCH_TOOL } from "./tool";
|
|
3
|
-
|
|
4
|
-
async function checkSubscriptionAccess(
|
|
5
|
-
apiKey: string,
|
|
6
|
-
): Promise<{ ok: true } | { ok: false; reason: string }> {
|
|
7
|
-
try {
|
|
8
|
-
const response = await fetch("https://api.synthetic.new/v2/quotas", {
|
|
9
|
-
method: "GET",
|
|
10
|
-
headers: {
|
|
11
|
-
Authorization: `Bearer ${apiKey}`,
|
|
12
|
-
},
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
if (!response.ok) {
|
|
16
|
-
return {
|
|
17
|
-
ok: false,
|
|
18
|
-
reason: `Quotas check failed (HTTP ${response.status})`,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const data = await response.json();
|
|
23
|
-
if (data?.subscription?.limit > 0) {
|
|
24
|
-
return { ok: true };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return {
|
|
28
|
-
ok: false,
|
|
29
|
-
reason: "No active subscription (search requires a subscription plan)",
|
|
30
|
-
};
|
|
31
|
-
} catch (error) {
|
|
32
|
-
const message =
|
|
33
|
-
error instanceof Error ? error.message : "Unknown error occurred";
|
|
34
|
-
return { ok: false, reason: `Quotas check failed: ${message}` };
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function registerSyntheticWebSearchHooks(pi: ExtensionAPI): void {
|
|
39
|
-
let accessCheckPromise:
|
|
40
|
-
| Promise<{ ok: true } | { ok: false; reason: string }>
|
|
41
|
-
| undefined;
|
|
42
|
-
let hasAccess = false;
|
|
43
|
-
let deniedReason: string | undefined;
|
|
44
|
-
let didNotifyDenied = false;
|
|
45
|
-
|
|
46
|
-
// Keep tool inactive at session start. Availability is decided before each agent run.
|
|
47
|
-
pi.on("session_start", () => {
|
|
48
|
-
const current = pi.getActiveTools();
|
|
49
|
-
if (current.includes(SYNTHETIC_WEB_SEARCH_TOOL)) {
|
|
50
|
-
pi.setActiveTools(
|
|
51
|
-
current.filter((toolName) => toolName !== SYNTHETIC_WEB_SEARCH_TOOL),
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Verify subscription only when user starts agent execution.
|
|
57
|
-
pi.on("before_agent_start", async (_event, ctx) => {
|
|
58
|
-
const apiKey = process.env.SYNTHETIC_API_KEY;
|
|
59
|
-
if (!apiKey) {
|
|
60
|
-
hasAccess = false;
|
|
61
|
-
deniedReason = "SYNTHETIC_API_KEY is not configured";
|
|
62
|
-
accessCheckPromise = undefined;
|
|
63
|
-
} else {
|
|
64
|
-
if (deniedReason === "SYNTHETIC_API_KEY is not configured") {
|
|
65
|
-
deniedReason = undefined;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!hasAccess && !deniedReason) {
|
|
69
|
-
accessCheckPromise ??= checkSubscriptionAccess(apiKey);
|
|
70
|
-
const access = await accessCheckPromise;
|
|
71
|
-
|
|
72
|
-
if (!access.ok) {
|
|
73
|
-
deniedReason = access.reason;
|
|
74
|
-
} else {
|
|
75
|
-
hasAccess = true;
|
|
76
|
-
didNotifyDenied = false;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (deniedReason) {
|
|
82
|
-
const current = pi.getActiveTools();
|
|
83
|
-
if (current.includes(SYNTHETIC_WEB_SEARCH_TOOL)) {
|
|
84
|
-
pi.setActiveTools(
|
|
85
|
-
current.filter((toolName) => toolName !== SYNTHETIC_WEB_SEARCH_TOOL),
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (ctx.hasUI && !didNotifyDenied) {
|
|
90
|
-
ctx.ui.notify(
|
|
91
|
-
`Synthetic web search disabled: ${deniedReason}`,
|
|
92
|
-
"warning",
|
|
93
|
-
);
|
|
94
|
-
didNotifyDenied = true;
|
|
95
|
-
}
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const current = pi.getActiveTools();
|
|
100
|
-
if (!current.includes(SYNTHETIC_WEB_SEARCH_TOOL)) {
|
|
101
|
-
pi.setActiveTools([...current, SYNTHETIC_WEB_SEARCH_TOOL]);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
}
|