@aliou/pi-ts-aperture 0.5.0 → 0.6.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 +119 -21
- package/extensions/aperture/dedicated/api-routing.ts +66 -0
- package/extensions/aperture/dedicated/model-defaults.ts +48 -0
- package/extensions/aperture/dedicated/runtime.ts +87 -0
- package/extensions/aperture/index.ts +78 -0
- package/extensions/aperture/onboarding/index.ts +25 -0
- package/extensions/aperture/onboarding/onboarding.ts +892 -0
- package/extensions/aperture/onboarding/setup-command.ts +53 -0
- package/extensions/aperture/onboarding/setup-wizard.ts +134 -0
- package/extensions/aperture/proxy/runtime.ts +160 -0
- package/extensions/aperture/settings-command.ts +369 -0
- package/extensions/aperture/shared/config/defaults.ts +17 -0
- package/extensions/aperture/shared/config/loader.ts +21 -0
- package/extensions/aperture/shared/config/migration/001-legacy-to-v0-6.ts +45 -0
- package/extensions/aperture/shared/config/migration/002-mode-to-capabilities.ts +20 -0
- package/extensions/aperture/shared/config/migration/003-normalize-capabilities.ts +26 -0
- package/extensions/aperture/shared/config/migration/index.ts +15 -0
- package/extensions/aperture/shared/config/types.ts +57 -0
- package/extensions/aperture/shared/sync-bus.ts +12 -0
- package/{src/lib → extensions/aperture/shared}/types.ts +1 -1
- package/package.json +37 -27
- package/src/api/client.ts +139 -0
- package/src/api/types.ts +26 -0
- package/src/provider-mapping.ts +91 -0
- package/src/url.ts +52 -0
- package/src/commands/settings.ts +0 -135
- package/src/commands/setup.ts +0 -232
- package/src/extension/runtime.ts +0 -122
- package/src/index.ts +0 -97
- package/src/lib/config.ts +0 -32
- package/src/lib/gateway.ts +0 -42
- package/src/lib/url.ts +0 -42
package/src/commands/setup.ts
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* aperture:setup -- interactive wizard for configuring Aperture.
|
|
3
|
-
*
|
|
4
|
-
* Steps:
|
|
5
|
-
* 1. URL input (health check runs inline on Enter, auto-advances on success)
|
|
6
|
-
* 2. Provider selection with per-provider "verify models" sub-option
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
FuzzyMultiSelector,
|
|
11
|
-
type FuzzyMultiSelectorItem,
|
|
12
|
-
getSettingsTheme,
|
|
13
|
-
type SettingsTheme,
|
|
14
|
-
Wizard,
|
|
15
|
-
type WizardStepContext,
|
|
16
|
-
} from "@aliou/pi-utils-settings";
|
|
17
|
-
import type {
|
|
18
|
-
ExtensionAPI,
|
|
19
|
-
ExtensionContext,
|
|
20
|
-
} from "@mariozechner/pi-coding-agent";
|
|
21
|
-
import type { Component, TUI } from "@mariozechner/pi-tui";
|
|
22
|
-
import { Input } from "@mariozechner/pi-tui";
|
|
23
|
-
import { configLoader } from "../lib/config";
|
|
24
|
-
import { checkApertureHealth } from "../lib/gateway";
|
|
25
|
-
import { normalizeInputUrl } from "../lib/url";
|
|
26
|
-
|
|
27
|
-
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
28
|
-
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// Step 1: URL input with inline health check
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
class UrlStep implements Component {
|
|
34
|
-
private input: Input;
|
|
35
|
-
private theme: SettingsTheme;
|
|
36
|
-
private tui: TUI;
|
|
37
|
-
private wizCtx: WizardStepContext;
|
|
38
|
-
private onUrl: (url: string) => void;
|
|
39
|
-
private readonly placeholder = "ai.pango-lin.ts.net";
|
|
40
|
-
|
|
41
|
-
private state: "idle" | "checking" | "ok" | "error" = "idle";
|
|
42
|
-
private errorMessage = "";
|
|
43
|
-
private frame = 0;
|
|
44
|
-
private timer: ReturnType<typeof setInterval> | null = null;
|
|
45
|
-
|
|
46
|
-
constructor(
|
|
47
|
-
theme: SettingsTheme,
|
|
48
|
-
tui: TUI,
|
|
49
|
-
currentValue: string,
|
|
50
|
-
wizCtx: WizardStepContext,
|
|
51
|
-
onUrl: (url: string) => void,
|
|
52
|
-
) {
|
|
53
|
-
this.theme = theme;
|
|
54
|
-
this.tui = tui;
|
|
55
|
-
this.wizCtx = wizCtx;
|
|
56
|
-
this.onUrl = onUrl;
|
|
57
|
-
this.input = new Input();
|
|
58
|
-
if (currentValue) {
|
|
59
|
-
this.input.setValue(currentValue);
|
|
60
|
-
}
|
|
61
|
-
this.input.onSubmit = () => this.submit();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private submit(): void {
|
|
65
|
-
const value = this.input.getValue().trim();
|
|
66
|
-
if (!value || this.state === "checking") return;
|
|
67
|
-
|
|
68
|
-
const url = normalizeInputUrl(value);
|
|
69
|
-
this.state = "checking";
|
|
70
|
-
this.frame = 0;
|
|
71
|
-
|
|
72
|
-
this.timer = setInterval(() => {
|
|
73
|
-
this.frame = (this.frame + 1) % SPINNER_FRAMES.length;
|
|
74
|
-
this.tui.requestRender();
|
|
75
|
-
}, 80);
|
|
76
|
-
|
|
77
|
-
checkApertureHealth(url).then((res) => {
|
|
78
|
-
if (this.timer) clearInterval(this.timer);
|
|
79
|
-
this.timer = null;
|
|
80
|
-
|
|
81
|
-
if (res.ok) {
|
|
82
|
-
this.state = "ok";
|
|
83
|
-
this.onUrl(url);
|
|
84
|
-
this.wizCtx.markComplete();
|
|
85
|
-
this.tui.requestRender();
|
|
86
|
-
setTimeout(() => this.wizCtx.goNext(), 400);
|
|
87
|
-
} else {
|
|
88
|
-
this.state = "error";
|
|
89
|
-
this.errorMessage = res.error ?? "unknown error";
|
|
90
|
-
this.tui.requestRender();
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
render(width: number): string[] {
|
|
96
|
-
const lines: string[] = [];
|
|
97
|
-
|
|
98
|
-
lines.push(
|
|
99
|
-
this.theme.hint(` Aperture base URL (e.g. ${this.placeholder}):`),
|
|
100
|
-
);
|
|
101
|
-
lines.push(` ${this.input.render(width - 4).join("")}`);
|
|
102
|
-
lines.push("");
|
|
103
|
-
|
|
104
|
-
if (this.state === "checking") {
|
|
105
|
-
const spinner = SPINNER_FRAMES[this.frame];
|
|
106
|
-
lines.push(this.theme.hint(` ${spinner} Checking connection...`));
|
|
107
|
-
} else if (this.state === "ok") {
|
|
108
|
-
lines.push(this.theme.hint(" Connected."));
|
|
109
|
-
} else if (this.state === "error") {
|
|
110
|
-
lines.push(this.theme.hint(` Could not connect: ${this.errorMessage}`));
|
|
111
|
-
lines.push(this.theme.hint(" Fix the URL and press Enter to retry."));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return lines;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
invalidate(): void {}
|
|
118
|
-
|
|
119
|
-
handleInput(data: string): void {
|
|
120
|
-
if (this.state === "checking") return;
|
|
121
|
-
this.state = "idle";
|
|
122
|
-
this.input.handleInput(data);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
dispose(): void {
|
|
126
|
-
if (this.timer) clearInterval(this.timer);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
|
-
// Command registration
|
|
132
|
-
// ---------------------------------------------------------------------------
|
|
133
|
-
|
|
134
|
-
export function registerSetupCommand(
|
|
135
|
-
pi: ExtensionAPI,
|
|
136
|
-
onSync: (ctx: ExtensionContext) => void,
|
|
137
|
-
): void {
|
|
138
|
-
pi.registerCommand("aperture:setup", {
|
|
139
|
-
description: "Configure Tailscale Aperture integration",
|
|
140
|
-
handler: async (_args, ctx) => {
|
|
141
|
-
if (!ctx.hasUI) {
|
|
142
|
-
ctx.ui.notify(
|
|
143
|
-
"aperture:setup requires an interactive terminal",
|
|
144
|
-
"error",
|
|
145
|
-
);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const config = configLoader.getConfig();
|
|
150
|
-
const checkGatewayProviders = config.checkGatewayModels ?? [];
|
|
151
|
-
|
|
152
|
-
const knownProviders = Array.from(
|
|
153
|
-
new Set(ctx.modelRegistry.getAll().map((model) => model.provider)),
|
|
154
|
-
).sort((a, b) => a.localeCompare(b));
|
|
155
|
-
|
|
156
|
-
let baseUrl = config.baseUrl;
|
|
157
|
-
|
|
158
|
-
const providerItems: FuzzyMultiSelectorItem[] = knownProviders.map(
|
|
159
|
-
(p) => ({
|
|
160
|
-
label: p,
|
|
161
|
-
checked: config.providers.includes(p),
|
|
162
|
-
subOptions: [
|
|
163
|
-
{
|
|
164
|
-
label: "verify models on gateway",
|
|
165
|
-
description:
|
|
166
|
-
"Warn at startup if this provider's models are missing from the Aperture gateway",
|
|
167
|
-
checked: checkGatewayProviders.includes(p),
|
|
168
|
-
},
|
|
169
|
-
],
|
|
170
|
-
}),
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
const confirmed = await ctx.ui.custom<boolean | undefined>(
|
|
174
|
-
(tui, theme, _kb, done) => {
|
|
175
|
-
const settingsTheme = getSettingsTheme(theme);
|
|
176
|
-
|
|
177
|
-
return new Wizard({
|
|
178
|
-
title: "Aperture Setup",
|
|
179
|
-
theme: settingsTheme,
|
|
180
|
-
minContentHeight: 16,
|
|
181
|
-
steps: [
|
|
182
|
-
{
|
|
183
|
-
label: "URL",
|
|
184
|
-
build: (wCtx: WizardStepContext) =>
|
|
185
|
-
new UrlStep(settingsTheme, tui, baseUrl, wCtx, (url) => {
|
|
186
|
-
baseUrl = url;
|
|
187
|
-
}),
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
label: "Providers",
|
|
191
|
-
build: (wCtx: WizardStepContext) => {
|
|
192
|
-
wCtx.markComplete();
|
|
193
|
-
return new FuzzyMultiSelector({
|
|
194
|
-
label: "Providers to route through Aperture",
|
|
195
|
-
items: providerItems,
|
|
196
|
-
theme: settingsTheme,
|
|
197
|
-
showHints: false,
|
|
198
|
-
showCount: false,
|
|
199
|
-
maxVisible: 7,
|
|
200
|
-
});
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
],
|
|
204
|
-
onComplete: () => done(true),
|
|
205
|
-
onCancel: () => done(undefined),
|
|
206
|
-
});
|
|
207
|
-
},
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
if (!confirmed) return;
|
|
211
|
-
|
|
212
|
-
const providers = providerItems
|
|
213
|
-
.filter((i) => i.checked)
|
|
214
|
-
.map((i) => i.label);
|
|
215
|
-
|
|
216
|
-
const checkGatewayModels = providerItems
|
|
217
|
-
.filter((i) => i.checked && i.subOptions?.[0]?.checked)
|
|
218
|
-
.map((i) => i.label);
|
|
219
|
-
|
|
220
|
-
await configLoader.save("global", {
|
|
221
|
-
baseUrl,
|
|
222
|
-
providers,
|
|
223
|
-
checkGatewayModels,
|
|
224
|
-
});
|
|
225
|
-
onSync(ctx);
|
|
226
|
-
ctx.ui.notify(
|
|
227
|
-
`Aperture configured: ${providers.length} provider(s) via ${baseUrl}`,
|
|
228
|
-
"info",
|
|
229
|
-
);
|
|
230
|
-
},
|
|
231
|
-
});
|
|
232
|
-
}
|
package/src/extension/runtime.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ApertureRuntime -- core extension runtime logic.
|
|
3
|
-
*
|
|
4
|
-
* Handles provider registration, unregistration, and gateway model checking.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { getApiProvider } from "@mariozechner/pi-ai";
|
|
8
|
-
import { configLoader } from "../lib/config";
|
|
9
|
-
import { fetchGatewayModelIds } from "../lib/gateway";
|
|
10
|
-
import type {
|
|
11
|
-
Api,
|
|
12
|
-
AssistantMessageEventStream,
|
|
13
|
-
CheckDeps,
|
|
14
|
-
Context,
|
|
15
|
-
Model,
|
|
16
|
-
SimpleStreamOptions,
|
|
17
|
-
SyncDeps,
|
|
18
|
-
} from "../lib/types";
|
|
19
|
-
import { resolveProviderBaseUrl } from "../lib/url";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Preserve provenance similarly to pi-synthetic so downstream providers can
|
|
23
|
-
* attribute traffic to Pi / this extension.
|
|
24
|
-
*/
|
|
25
|
-
const APERTURE_PROVENANCE_HEADERS = {
|
|
26
|
-
Referer: "https://pi.dev",
|
|
27
|
-
"X-Title": "npm:@aliou/pi-ts-aperture",
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
function resolveProviderHeaders(models: Model<Api>[]): Record<string, string> {
|
|
31
|
-
const modelHeaders = models.find((m) => m.headers)?.headers ?? {};
|
|
32
|
-
return {
|
|
33
|
-
...APERTURE_PROVENANCE_HEADERS,
|
|
34
|
-
...modelHeaders,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export class ApertureRuntime {
|
|
39
|
-
private registeredProviders = new Set<string>();
|
|
40
|
-
|
|
41
|
-
async sync(deps: SyncDeps): Promise<void> {
|
|
42
|
-
const config = configLoader.getConfig();
|
|
43
|
-
if (!config.baseUrl || config.providers.length === 0) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const baseUrl = resolveProviderBaseUrl(config);
|
|
48
|
-
if (!baseUrl) return;
|
|
49
|
-
|
|
50
|
-
const allModels = deps.getModels();
|
|
51
|
-
|
|
52
|
-
for (const providerName of config.providers) {
|
|
53
|
-
const providerModels = allModels.filter(
|
|
54
|
-
(m) => m.provider === providerName,
|
|
55
|
-
);
|
|
56
|
-
if (providerModels.length === 0) continue;
|
|
57
|
-
|
|
58
|
-
const api = providerModels[0].api ?? "openai-completions";
|
|
59
|
-
const builtIn = getApiProvider(api);
|
|
60
|
-
|
|
61
|
-
deps.registerProvider(providerName, {
|
|
62
|
-
baseUrl,
|
|
63
|
-
apiKey: "-",
|
|
64
|
-
headers: resolveProviderHeaders(providerModels),
|
|
65
|
-
api,
|
|
66
|
-
streamSimple: builtIn
|
|
67
|
-
? (
|
|
68
|
-
model: Model<Api>,
|
|
69
|
-
context: Context,
|
|
70
|
-
options?: SimpleStreamOptions,
|
|
71
|
-
): AssistantMessageEventStream =>
|
|
72
|
-
builtIn.streamSimple(model, context, {
|
|
73
|
-
...options,
|
|
74
|
-
headers: {
|
|
75
|
-
...options?.headers,
|
|
76
|
-
"x-session-id": options?.sessionId ?? "",
|
|
77
|
-
},
|
|
78
|
-
})
|
|
79
|
-
: undefined,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
this.registeredProviders.add(providerName);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async checkMissingModels(deps: CheckDeps, gatewayUrl: string): Promise<void> {
|
|
87
|
-
const config = configLoader.getConfig();
|
|
88
|
-
if (config.checkGatewayModels.length === 0) return;
|
|
89
|
-
|
|
90
|
-
const gatewayModelIds = await fetchGatewayModelIds(gatewayUrl);
|
|
91
|
-
if (gatewayModelIds.length === 0) return;
|
|
92
|
-
|
|
93
|
-
const allModels = deps.getModels();
|
|
94
|
-
const checkedProviders = new Set(config.checkGatewayModels);
|
|
95
|
-
|
|
96
|
-
const routedModels = allModels.filter((m) =>
|
|
97
|
-
checkedProviders.has(m.provider),
|
|
98
|
-
);
|
|
99
|
-
const missingModels = routedModels.filter(
|
|
100
|
-
(m) => !gatewayModelIds.includes(m.id),
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
if (missingModels.length > 0) {
|
|
104
|
-
const ids = missingModels.map((m) => m.id).join(", ");
|
|
105
|
-
deps.notify(
|
|
106
|
-
`[aperture] models not available on gateway: ${ids}. Add them to the gateway configuration.`,
|
|
107
|
-
"warning",
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Returns providers that should be unregistered based on config changes.
|
|
114
|
-
* Compares previous providers with new ones.
|
|
115
|
-
*/
|
|
116
|
-
getProvidersToUnregister(
|
|
117
|
-
prevProviders: string[],
|
|
118
|
-
nextProviders: string[],
|
|
119
|
-
): string[] {
|
|
120
|
-
return prevProviders.filter((p) => !nextProviders.includes(p));
|
|
121
|
-
}
|
|
122
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pi extension for Tailscale Aperture integration.
|
|
3
|
-
*
|
|
4
|
-
* Entry point orchestration:
|
|
5
|
-
* - Load config
|
|
6
|
-
* - Register session_start hook for provider registration
|
|
7
|
-
* - Register user commands
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type {
|
|
11
|
-
ExtensionAPI,
|
|
12
|
-
ExtensionContext,
|
|
13
|
-
} from "@mariozechner/pi-coding-agent";
|
|
14
|
-
import { registerApertureSettings } from "./commands/settings";
|
|
15
|
-
import { registerSetupCommand } from "./commands/setup";
|
|
16
|
-
import { ApertureRuntime } from "./extension/runtime";
|
|
17
|
-
import { configLoader } from "./lib/config";
|
|
18
|
-
import { resolveGatewayUrl } from "./lib/url";
|
|
19
|
-
|
|
20
|
-
export default async function (pi: ExtensionAPI): Promise<void> {
|
|
21
|
-
await configLoader.load();
|
|
22
|
-
|
|
23
|
-
const runtime = new ApertureRuntime();
|
|
24
|
-
let lastRegisteredProviders: string[] = [
|
|
25
|
-
...configLoader.getConfig().providers,
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
// Sync function used by commands after config changes
|
|
29
|
-
const onSync = (ctx: ExtensionContext): void => {
|
|
30
|
-
const config = configLoader.getConfig();
|
|
31
|
-
|
|
32
|
-
// Unregister providers that were removed from config
|
|
33
|
-
const prevProviders = lastRegisteredProviders;
|
|
34
|
-
const nextProviders = config.providers;
|
|
35
|
-
const toRemove = runtime.getProvidersToUnregister(
|
|
36
|
-
prevProviders,
|
|
37
|
-
nextProviders,
|
|
38
|
-
);
|
|
39
|
-
for (const provider of toRemove) {
|
|
40
|
-
pi.unregisterProvider(provider);
|
|
41
|
-
ctx.ui.notify(
|
|
42
|
-
`[aperture] unregistered ${provider}. Run /reload to use the native provider.`,
|
|
43
|
-
"info",
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Re-register providers
|
|
48
|
-
void runtime
|
|
49
|
-
.sync({
|
|
50
|
-
registerProvider: pi.registerProvider.bind(pi),
|
|
51
|
-
getModels: () => ctx.modelRegistry.getAll(),
|
|
52
|
-
})
|
|
53
|
-
.then(() => {
|
|
54
|
-
// Refresh active model if it's from a registered provider
|
|
55
|
-
if (
|
|
56
|
-
ctx.model &&
|
|
57
|
-
ctx.modelRegistry.find(ctx.model.provider, ctx.model.id)
|
|
58
|
-
) {
|
|
59
|
-
const updated = ctx.modelRegistry.find(
|
|
60
|
-
ctx.model.provider,
|
|
61
|
-
ctx.model.id,
|
|
62
|
-
);
|
|
63
|
-
if (updated && config.providers.includes(ctx.model.provider)) {
|
|
64
|
-
void pi.setModel(updated);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// Check for missing models on gateway if configured
|
|
70
|
-
if (config.checkGatewayModels.length > 0) {
|
|
71
|
-
const gatewayUrl = resolveGatewayUrl(config);
|
|
72
|
-
if (gatewayUrl) {
|
|
73
|
-
void runtime.checkMissingModels(
|
|
74
|
-
{
|
|
75
|
-
getModels: () => ctx.modelRegistry.getAll(),
|
|
76
|
-
notify: (msg, type) => ctx.ui.notify(msg, type),
|
|
77
|
-
},
|
|
78
|
-
gatewayUrl,
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
lastRegisteredProviders = [...nextProviders];
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// Register providers at session start (for new sessions)
|
|
87
|
-
pi.on("session_start", (_event, ctx) => {
|
|
88
|
-
lastRegisteredProviders = [...configLoader.getConfig().providers];
|
|
89
|
-
void runtime.sync({
|
|
90
|
-
registerProvider: pi.registerProvider.bind(pi),
|
|
91
|
-
getModels: () => ctx.modelRegistry.getAll(),
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
registerSetupCommand(pi, onSync);
|
|
96
|
-
registerApertureSettings(pi, onSync);
|
|
97
|
-
}
|
package/src/lib/config.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration schema and loader for the Aperture extension.
|
|
3
|
-
*
|
|
4
|
-
* ApertureConfig is the user-facing schema (all fields optional).
|
|
5
|
-
* ResolvedConfig is the internal schema (all fields required, defaults applied).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { ConfigLoader } from "@aliou/pi-utils-settings";
|
|
9
|
-
|
|
10
|
-
export interface ApertureConfig {
|
|
11
|
-
baseUrl?: string;
|
|
12
|
-
providers?: string[];
|
|
13
|
-
checkGatewayModels?: string[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ResolvedConfig {
|
|
17
|
-
baseUrl: string;
|
|
18
|
-
providers: string[];
|
|
19
|
-
checkGatewayModels: string[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const DEFAULT_CONFIG: ResolvedConfig = {
|
|
23
|
-
baseUrl: "",
|
|
24
|
-
providers: [],
|
|
25
|
-
checkGatewayModels: [],
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export const configLoader = new ConfigLoader<ApertureConfig, ResolvedConfig>(
|
|
29
|
-
"aperture",
|
|
30
|
-
DEFAULT_CONFIG,
|
|
31
|
-
{ scopes: ["global"] },
|
|
32
|
-
);
|
package/src/lib/gateway.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gateway health and model checking.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface HealthCheckResult {
|
|
6
|
-
ok: boolean;
|
|
7
|
-
error?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export async function checkApertureHealth(
|
|
11
|
-
baseUrl: string,
|
|
12
|
-
): Promise<HealthCheckResult> {
|
|
13
|
-
const url = `${baseUrl.replace(/\/+$/, "")}/v1/models`;
|
|
14
|
-
try {
|
|
15
|
-
const res = await fetch(url, {
|
|
16
|
-
method: "GET",
|
|
17
|
-
signal: AbortSignal.timeout(5000),
|
|
18
|
-
});
|
|
19
|
-
if (!res.ok) {
|
|
20
|
-
return { ok: false, error: `HTTP ${res.status} ${res.statusText}` };
|
|
21
|
-
}
|
|
22
|
-
return { ok: true };
|
|
23
|
-
} catch (e: unknown) {
|
|
24
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
25
|
-
return { ok: false, error: msg };
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function fetchGatewayModelIds(baseUrl: string): Promise<string[]> {
|
|
30
|
-
const url = `${baseUrl.replace(/\/+$/, "")}/v1/models`;
|
|
31
|
-
try {
|
|
32
|
-
const res = await fetch(url, {
|
|
33
|
-
method: "GET",
|
|
34
|
-
signal: AbortSignal.timeout(5000),
|
|
35
|
-
});
|
|
36
|
-
if (!res.ok) return [];
|
|
37
|
-
const body = (await res.json()) as { data?: { id: string }[] };
|
|
38
|
-
return body.data?.map((m) => m.id) ?? [];
|
|
39
|
-
} catch {
|
|
40
|
-
return [];
|
|
41
|
-
}
|
|
42
|
-
}
|
package/src/lib/url.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure URL helpers.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { ApertureConfig } from "./config";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Normalizes a user-input URL:
|
|
9
|
-
* - Trims whitespace
|
|
10
|
-
* - Adds http:// scheme if missing
|
|
11
|
-
* - Strips trailing /v1 or /v1/
|
|
12
|
-
* - Strips trailing slashes
|
|
13
|
-
*/
|
|
14
|
-
export function normalizeInputUrl(raw: string): string {
|
|
15
|
-
let result = raw.trim();
|
|
16
|
-
if (!result) return result;
|
|
17
|
-
if (!result.startsWith("http://") && !result.startsWith("https://")) {
|
|
18
|
-
result = `http://${result}`;
|
|
19
|
-
}
|
|
20
|
-
return result.replace(/\/v1\/?$/, "").replace(/\/+$/, "");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Returns configured gateway URL without trailing slash.
|
|
25
|
-
* Returns null when baseUrl is empty or providers list is empty.
|
|
26
|
-
*/
|
|
27
|
-
export function resolveGatewayUrl(config: ApertureConfig): string | null {
|
|
28
|
-
const { baseUrl, providers } = config;
|
|
29
|
-
if (!baseUrl || providers?.length === 0) return null;
|
|
30
|
-
return baseUrl.replace(/\/+$/, "");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Returns the Aperture provider base URL used for provider registration.
|
|
35
|
-
* Appends /v1 to the gateway URL.
|
|
36
|
-
* Returns null when gateway URL cannot be resolved.
|
|
37
|
-
*/
|
|
38
|
-
export function resolveProviderBaseUrl(config: ApertureConfig): string | null {
|
|
39
|
-
const gateway = resolveGatewayUrl(config);
|
|
40
|
-
if (!gateway) return null;
|
|
41
|
-
return `${gateway}/v1`;
|
|
42
|
-
}
|