Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion server/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ The barrel `server/lib/index.js` is a machine-checkable enumeration of every pub

| Module | Purpose |
|---|---|
| `aiToolkit/` | Vendored toolkit (providers + runner + prompts + status). See `aiToolkit/index.js`. |
| `aiToolkit/` | Vendored toolkit (providers + runner + prompts + status). See `aiToolkit/index.js`. `aiToolkit/endpointGuard.js` — a peer of `aiToolkit/errorDetection.js`, not under `internal/` — publicly exports `evaluateSecretEndpoint`/`assertSecretEndpoint`, the SSRF/key-exfiltration guard for provider `endpoint` URLs; PortOS's own `server/services/*` should import both directly from there (also re-exported through `aiToolkit/index.js` for barrel completeness, but that pulls the toolkit's much larger route/provider/runner graph — not worth it for just this guard). |
| `aiToolkitState.js` | Module-level singleton for the toolkit instance shared by the `providers`/`runner`/`promptService` shims — `setAIToolkitInstance` / `requireToolkit` (throws `AI_TOOLKIT_NOT_INITIALIZED`) / `getAIToolkitInstance` (no-throw for cleanup paths). |
| `antigravity.js` | Antigravity (`agy`) CLI provider helpers — id/sentinel constants (`ANTIGRAVITY_CLI_ID`, `ANTIGRAVITY_CONFIGURED_DEFAULT`, `LEGACY_GEMINI_*`), `isAntigravityCommand`/`isAntigravityCliProvider` predicates, and `ensureAntigravityPrintArgs(args, {model, effort})`/`ensureAntigravityTuiArgs(args, {model, effort})`/`stripAntigravityUnsupportedArgs` argv normalizers. `parseAntigravityModelList(stdout)` parses `agy models` rows — accepts both the modern `<id>\t<Label>` shape and the older bare-id-per-line one, deduped, sentinel dropped (mirrored in the vendored toolkit's `internal/antigravity.js`; used by both the provider-catalog refresh and Image Gen's agy model picker). `isAntigravityModelId(id)` is the same id shape as a bare predicate, for spawn sites building an `agy --model` argv from a value no route schema bounded. The strip drops legacy Gemini `--yolo`/`-m`/`--output-format` but PRESERVES the long `--model` (agy accepts it as a per-session flag, so a user-baked pin is a real selection and suppresses the injected one); the two builders inject `--model`/`--effort` from the per-run overrides, always ahead of the trailing `--print` marker whose value is the prompt. |
| `llmText.js` | Pure LLM-output text helpers — `stripCodeFences` (unfence a model reply) and `parseLLMJSON` (unfence then JSON.parse with a descriptive throw). Below the provider layer, so lib can clean model output without importing provider orchestration. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
* — a bearer token has no business going there and it's the canonical SSRF
* credential-theft target.
*
* Kept in `aiToolkit/internal/` (pure, no imports) so the toolkit stays
* self-contained; `server/services/aiProvider.js` imports it from here too.
* Pure (no imports), and lives at the toolkit's own root — a peer of
* `errorDetection.js` — rather than under `internal/`, so PortOS's own
* `server/services/*` (six modules, at #5619) can import it directly
* without pulling the full toolkit barrel's much larger graph (routes,
* providers, runner) just for this one guard (#5625).
*
* NOTE: checks are performed on the URL's literal host. We do not resolve DNS,
* so a user-supplied hostname that resolves to a private/metadata IP is treated
Expand Down
1 change: 1 addition & 0 deletions server/lib/aiToolkit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const DEFAULT_PROVIDERS_SAMPLE = join(__dirname, 'defaults/providers.samp

export * from './validation.js';
export * from './errorDetection.js';
export * from './endpointGuard.js';
export * from './constants.js';
export { createProviderService, createRunnerService, createPromptsService, createProviderStatusService };
export { isOllamaBackedProvider, canRefreshModels, ollamaRefreshGroupKey };
Expand Down
4 changes: 2 additions & 2 deletions server/lib/aiToolkit/providers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { readFile, rename } from 'fs/promises';
import { existsSync } from 'fs';
import { join, dirname, delimiter, isAbsolute } from 'path';
import { atomicWrite } from './internal/atomicWrite.js';
import { assertSecretEndpoint, evaluateSecretEndpoint } from './internal/endpointGuard.js';
import { assertSecretEndpoint, evaluateSecretEndpoint } from './endpointGuard.js';
import { fileURLToPath } from 'url';
import { execFile } from 'child_process';
import { promisify } from 'util';
Expand Down Expand Up @@ -663,7 +663,7 @@ export function createProviderService(config = {}) {
? { gatewayBacked: providerData.gatewayBacked } : {}),
...(providerData.orcarouterBacked === true ? { orcarouterBacked: true } : {}),
// Explicit opt-in to send the API key to an arbitrary (non-local,
// non-allowlisted) endpoint — see internal/endpointGuard.js. Only
// non-allowlisted) endpoint — see endpointGuard.js. Only
// persisted when true so existing keyless/local providers stay clean.
...(providerData.allowCustomEndpoint === true ? { allowCustomEndpoint: true } : {}),
envVars: providerData.envVars || {},
Expand Down
2 changes: 1 addition & 1 deletion server/lib/aiToolkit/runner.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { mkdir, readFile, readdir, rm } from 'fs/promises';
import { atomicWrite } from './internal/atomicWrite.js';
import { evaluateSecretEndpoint } from './internal/endpointGuard.js';
import { evaluateSecretEndpoint } from './endpointGuard.js';
import { existsSync } from 'fs';
import { join, extname, basename, isAbsolute, delimiter } from 'path';
import { spawn, ChildProcess } from 'child_process';
Expand Down
2 changes: 1 addition & 1 deletion server/lib/aiToolkit/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export const providerSchema = z.object({
// Explicit opt-in to attach the provider's API key to an arbitrary
// (non-local, non-allowlisted) endpoint. Guards against SSRF / key
// exfiltration to a hostile or mistyped host — see
// internal/endpointGuard.js. Metadata endpoints stay blocked even when true.
// endpointGuard.js. Metadata endpoints stay blocked even when true.
allowCustomEndpoint: z.boolean().optional(),
envVars: z.record(z.string()).optional(),
secretEnvVars: z.array(z.string()).optional(),
Expand Down
2 changes: 1 addition & 1 deletion server/lib/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ export const providerSchema = z.object({
orcarouterBacked: z.boolean().optional(),
// Explicit opt-in to attach the API key to an arbitrary (non-local,
// non-allowlisted) endpoint — mirrors the aiToolkit providerSchema. Guards
// SSRF / key exfiltration (server/lib/aiToolkit/internal/endpointGuard.js).
// SSRF / key exfiltration (server/lib/aiToolkit/endpointGuard.js).
allowCustomEndpoint: z.boolean().optional(),
envVars: z.record(z.string()).optional(),
headlessArgs: z.array(z.string()).optional(),
Expand Down
2 changes: 1 addition & 1 deletion server/services/aiProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { readResponseJson } from '../lib/readResponseJson.js';
import { classifyCodexTransportError, isCodexTextTransportEnabled } from '../lib/codexTurn.js';
import { resolveBenchWaitMs, resolveProviderBench } from '../lib/providerCooldown.js';
import { ERROR_CATEGORIES } from '../lib/aiToolkit/errorDetection.js';
import { evaluateSecretEndpoint } from '../lib/aiToolkit/internal/endpointGuard.js';
import { evaluateSecretEndpoint } from '../lib/aiToolkit/endpointGuard.js';
import { withCreativeLatitude } from '../lib/creativeLatitude.js';
Comment thread
atomantic marked this conversation as resolved.

const isAPI = (p) => p && p.type === 'api' && p.enabled !== false;
Expand Down
2 changes: 1 addition & 1 deletion server/services/askService.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { prepareCliPrompt } from '../lib/cliProviderArgs.js';
import { prepareCliSpawn } from '../lib/bufferedSpawn.js';
import { buildCliChildEnv } from '../lib/cliChildEnv.js';
import { ensureProviderReady as ensureOllamaProviderReady } from './ollamaManager.js';
import { evaluateSecretEndpoint } from '../lib/aiToolkit/internal/endpointGuard.js';
import { evaluateSecretEndpoint } from '../lib/aiToolkit/endpointGuard.js';
import { iterateOpenAiChat } from '../lib/openAiChatStream.js';

// Re-export so the route can keep importing modes via askService — but
Expand Down
2 changes: 1 addition & 1 deletion server/services/localLlmPlayground.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { anyAbortSignal } from '../lib/requestAbort.js';
// The SSE read loop lives in `lib/openAiChatStream.js` so the assessments
// service can measure a bare loopback daemon that has no provider record.
import { buildMessages, streamOllamaChat, streamOpenAiChat } from '../lib/openAiChatStream.js';
import { assertSecretEndpoint } from '../lib/aiToolkit/internal/endpointGuard.js';
import { assertSecretEndpoint } from '../lib/aiToolkit/endpointGuard.js';

const PROVIDER_BY_BACKEND = { ollama: 'ollama', lmstudio: 'lmstudio' };

Expand Down
2 changes: 1 addition & 1 deletion server/services/visionTest.frameGuard.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ vi.mock('./providers.js', () => ({
vi.mock('../lib/fetchWithTimeout.js', () => ({ fetchWithTimeout: vi.fn() }));
vi.mock('./ollamaManager.js', () => ({ ensureProviderReady: vi.fn(async () => ({ success: true })) }));
vi.mock('./visionCli.js', () => ({ describeImageViaCli: vi.fn() }));
vi.mock('../lib/aiToolkit/internal/endpointGuard.js', () => ({
vi.mock('../lib/aiToolkit/endpointGuard.js', () => ({
assertSecretEndpoint: vi.fn(),
evaluateSecretEndpoint: vi.fn(() => ({ ok: true })),
}));
Expand Down
2 changes: 1 addition & 1 deletion server/services/visionTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { describeFrameStats, isDegenerateFrame } from '../lib/imageFrameStats.js
import { fetchWithTimeout } from '../lib/fetchWithTimeout.js';
import { ensureProviderReady as ensureOllamaProviderReady } from './ollamaManager.js';
import { describeImageViaCli } from './visionCli.js';
import { assertSecretEndpoint, evaluateSecretEndpoint } from '../lib/aiToolkit/internal/endpointGuard.js';
import { assertSecretEndpoint, evaluateSecretEndpoint } from '../lib/aiToolkit/endpointGuard.js';

const SCREENSHOTS_DIR = PATHS.screenshots;
const DEFAULT_VISION_TIMEOUT_MS = 60000;
Expand Down
2 changes: 1 addition & 1 deletion server/services/voice/llm.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// provider is missing, not API-type, or the toolkit hasn't warmed yet.

import { getProviderById } from '../providers.js';
import { assertSecretEndpoint } from '../../lib/aiToolkit/internal/endpointGuard.js';
import { assertSecretEndpoint } from '../../lib/aiToolkit/endpointGuard.js';

// Legacy env-based LM Studio default. Returns the OpenAI-compatible API base
// INCLUDING the version path, so callers append `/models` / `/chat/completions`.
Expand Down