diff --git a/src/backend/controllers/drivers/DriverController.test.ts b/src/backend/controllers/drivers/DriverController.test.ts
index a1168ef3e..1a4d90dee 100644
--- a/src/backend/controllers/drivers/DriverController.test.ts
+++ b/src/backend/controllers/drivers/DriverController.test.ts
@@ -319,10 +319,11 @@ describe('DriverController.#handleCall (via captured router)', () => {
).rejects.toMatchObject({ statusCode: 404 });
});
- it('rejects a bare session ("root" token) actor on a noUserSession driver with a helpful 403', async () => {
- // The AI drivers all set `noUserSession` — a session token must
- // not double as an AI credential. The message has to point the
- // caller at the credentials that DO work.
+ it('admits a bare session ("root" token) actor on the AI drivers', async () => {
+ // Privileged ("godmode") apps call AI with the user's own session
+ // token, so the AI drivers no longer set `noUserSession`: the
+ // session actor reaches the permission scan (403 forbidden — no
+ // perms granted) instead of the credential-shape rejection.
const actor = await makeUserActor();
const req = makeReq(
{
@@ -342,12 +343,11 @@ describe('DriverController.#handleCall (via captured router)', () => {
),
).rejects.toMatchObject({
statusCode: 403,
- legacyCode: 'app_or_api_token_required',
- message: expect.stringMatching(/app or worker token|API token/i),
+ legacyCode: 'forbidden',
});
});
- it('admits app and access-token actors past the noUserSession gate (they fail later on permission, not credential shape)', async () => {
+ it('admits app and access-token actors on the AI drivers (they fail later on permission, not credential shape)', async () => {
const base = await makeUserActor();
const delegatedActors: Actor[] = [
{ ...base, app: { uid: `app-${uuidv4()}` } },
@@ -387,7 +387,7 @@ describe('DriverController.#handleCall (via captured router)', () => {
}
});
- it('admits a user-scoped worker session past the noUserSession gate', async () => {
+ it('admits a user-scoped worker session on the AI drivers', async () => {
// Workers deployed without an app binding authenticate as a user
// actor whose session row is kind='worker' — they must keep their
// AI access (they fail later on permission here, not on
diff --git a/src/backend/controllers/drivers/DriverController.ts b/src/backend/controllers/drivers/DriverController.ts
index caccdeb2b..56a8b637c 100644
--- a/src/backend/controllers/drivers/DriverController.ts
+++ b/src/backend/controllers/drivers/DriverController.ts
@@ -3,18 +3,19 @@
*
* This file is part of Puter.
*
- * Puter is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * Puter is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
*
* You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
+ * along with this program. If not, see
+ * [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
*/
import type { Request, Response } from 'express';
@@ -116,17 +117,17 @@ const translateProviderError = (err: unknown): unknown => {
@Controller('/drivers')
export class DriverController extends PuterController {
- /** iface → Map */
+ /** Iface → Map */
#drivers = new Map>();
- /** iface → default driver name */
+ /** Iface → default driver name */
#defaults = new Map();
/**
- * driver instance → resolved meta. Cached so the per-call rate-limit
- * lookup doesn't have to walk prototype chains on every request.
+ * Driver instance → resolved meta. Cached so the per-call rate-limit lookup
+ * doesn't have to walk prototype chains on every request.
*/
#meta = new WeakMap();
/**
- * driver instance → the set of method names callable via `/drivers/call`.
+ * Driver instance → the set of method names callable via `/drivers/call`.
* Resolved once at registration (server startup) via
* `resolveCallableMethods`; the request path only does a `Set.has` lookup.
* This is what stops framework/lifecycle methods (`onServerStart`, etc.)
@@ -236,7 +237,7 @@ export class DriverController extends PuterController {
const driverMeta = this.#meta.get(driver);
- // Drivers flagged `noUserSession` (the AI drivers) refuse the bare
+ // Drivers flagged `noUserSession` refuse the bare
// account-session ("root") token: callers must present an app or
// worker token, or an API token minted from the dashboard. This is
// the per-driver counterpart of the `noUserSession` route option —
diff --git a/src/backend/drivers/ai-chat/ChatCompletionDriver.ts b/src/backend/drivers/ai-chat/ChatCompletionDriver.ts
index 4cdc41801..90ebc3f8b 100644
--- a/src/backend/drivers/ai-chat/ChatCompletionDriver.ts
+++ b/src/backend/drivers/ai-chat/ChatCompletionDriver.ts
@@ -3,18 +3,19 @@
*
* This file is part of Puter.
*
- * Puter is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * Puter is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
*
* You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
+ * along with this program. If not, see
+ * [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
*/
import crypto from 'node:crypto';
@@ -72,13 +73,13 @@ type ProviderAttempt = {
};
/**
- * Capture what an upstream provider gave us so the classifier downstream
- * can decide a user-facing status code instead of always returning 500.
+ * Capture what an upstream provider gave us so the classifier downstream can
+ * decide a user-facing status code instead of always returning 500.
*
- * OpenAI-SDK-based providers throw `APIError` with `.status` and a
- * structured `.error` body — pull both. For arbitrary errors we fall
- * back to the message and a status sniff so providers that throw plain
- * `Error("... 503 ...")` strings still classify correctly.
+ * OpenAI-SDK-based providers throw `APIError` with `.status` and a structured
+ * `.error` body — pull both. For arbitrary errors we fall back to the message
+ * and a status sniff so providers that throw plain `Error("... 503 ...")`
+ * strings still classify correctly.
*/
const toAttempt = (
modelId: string,
@@ -126,11 +127,12 @@ const isUpstream5xx = (a: ProviderAttempt) =>
* Map an exhausted fallback chain to a single user-facing HttpError.
*
* Per-class rules (see also alarm gate in server.ts):
- * - all rate-limited → 429 `upstream_rate_limited` (paged: forced alert)
- * - all auth failures → 500 `upstream_auth_failed` (paged: our config)
- * - all upstream 5xx → 400 `upstream_provider_unavailable` (no page)
- * - all upstream 4xx (other) → 400 `upstream_bad_request` (no page)
- * - mixed → 400 `upstream_failed` (no page)
+ *
+ * - All rate-limited → 429 `upstream_rate_limited` (paged: forced alert)
+ * - All auth failures → 500 `upstream_auth_failed` (paged: our config)
+ * - All upstream 5xx → 400 `upstream_provider_unavailable` (no page)
+ * - All upstream 4xx (other) → 400 `upstream_bad_request` (no page)
+ * - Mixed → 400 `upstream_failed` (no page)
*/
const classifyAttempts = (attempts: ProviderAttempt[]): HttpError => {
const fields = { attempts };
@@ -196,16 +198,15 @@ const classifyAttempts = (attempts: ProviderAttempt[]): HttpError => {
/**
* Driver implementing the `puter-chat-completion` interface.
*
- * Manages multiple upstream providers (Claude, OpenAI, …) and handles
- * model resolution, provider routing, fallback on failure, and message
- * normalisation. Each provider is a plain `IChatProvider` — the driver
- * instantiates them from config on boot.
+ * Manages multiple upstream providers (Claude, OpenAI, …) and handles model
+ * resolution, provider routing, fallback on failure, and message normalisation.
+ * Each provider is a plain `IChatProvider` — the driver instantiates them from
+ * config on boot.
*
* Providers handle their own metering internally.
*/
export class ChatCompletionDriver extends PuterDriver {
readonly driverInterface = 'puter-chat-completion';
- readonly noUserSession = true;
readonly driverName = 'ai-chat';
readonly isDefault = true;
diff --git a/src/backend/drivers/ai-image/ImageGenerationDriver.ts b/src/backend/drivers/ai-image/ImageGenerationDriver.ts
index 4f75093b5..e682bb750 100644
--- a/src/backend/drivers/ai-image/ImageGenerationDriver.ts
+++ b/src/backend/drivers/ai-image/ImageGenerationDriver.ts
@@ -3,18 +3,19 @@
*
* This file is part of Puter.
*
- * Puter is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * Puter is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
*
* You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
+ * along with this program. If not, see
+ * [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
*/
import crypto from 'node:crypto';
@@ -37,17 +38,16 @@ import type { IGenerateParams, IImageModel, IImageProvider } from './types.js';
/**
* Driver implementing the `puter-image-generation` interface.
*
- * Manages multiple upstream providers and routes `generate()` calls
- * based on the requested model. Mirrors ChatCompletionDriver's pattern:
- * providers are instantiated from config on boot, a model map is built
- * from each provider's declared models, and calls are dispatched.
+ * Manages multiple upstream providers and routes `generate()` calls based on
+ * the requested model. Mirrors ChatCompletionDriver's pattern: providers are
+ * instantiated from config on boot, a model map is built from each provider's
+ * declared models, and calls are dispatched.
*
- * Output is a URL string (web URL or data URI) — no streaming, no
- * TypedValue wrapper.
+ * Output is a URL string (web URL or data URI) — no streaming, no TypedValue
+ * wrapper.
*/
export class ImageGenerationDriver extends PuterDriver {
readonly driverInterface = 'puter-image-generation';
- readonly noUserSession = true;
readonly driverName = 'ai-image';
// puter-js's `txt2img` falls through `options.driver` into the
// driver-name slot (e.g. `xai-image-generation`), so alias all provider
diff --git a/src/backend/drivers/ai-ocr/OCRDriver.ts b/src/backend/drivers/ai-ocr/OCRDriver.ts
index c27ec1350..2c4f4b15a 100644
--- a/src/backend/drivers/ai-ocr/OCRDriver.ts
+++ b/src/backend/drivers/ai-ocr/OCRDriver.ts
@@ -3,18 +3,19 @@
*
* This file is part of Puter.
*
- * Puter is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * Puter is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
*
* You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
+ * along with this program. If not, see
+ * [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
*/
import {
@@ -33,9 +34,9 @@ import { loadFileInput, type LoadedFile } from '../util/fileInput.js';
import { OCR_COSTS } from './costs.js';
/**
- * Driver implementing `puter-ocr` — document OCR. Two providers:
- * • `aws-textract` — AWS Textract (region-aware clients; direct S3 source when available)
- * • `mistral` — Mistral OCR (URL/data-URL based)
+ * Driver implementing `puter-ocr` — document OCR. Two providers: •
+ * `aws-textract` — AWS Textract (region-aware clients; direct S3 source when
+ * available) • `mistral` — Mistral OCR (URL/data-URL based)
*/
interface RecognizeArgs {
source?: unknown;
@@ -79,7 +80,6 @@ interface MistralOcrClient {
export class OCRDriver extends PuterDriver {
readonly driverInterface = 'puter-ocr';
- readonly noUserSession = true;
readonly driverName = 'ai-ocr';
// Shared AI policy — see `drivers/util/aiLimits.ts` for the tier table.
@@ -117,9 +117,11 @@ export class OCRDriver extends PuterDriver {
const providers = this.config.providers ?? {};
const textract = providers['aws-textract'] as
- Record | undefined;
+ | Record
+ | undefined;
const textractAws = (textract?.aws ?? textract) as
- Record | undefined;
+ | Record
+ | undefined;
const textractAccessKey = textractAws?.access_key as string | undefined;
const textractSecretKey = textractAws?.secret_key as string | undefined;
const textractRegion =
diff --git a/src/backend/drivers/ai-speech2speech/VoiceChangerDriver.ts b/src/backend/drivers/ai-speech2speech/VoiceChangerDriver.ts
index b35f23b4d..ddb21e943 100644
--- a/src/backend/drivers/ai-speech2speech/VoiceChangerDriver.ts
+++ b/src/backend/drivers/ai-speech2speech/VoiceChangerDriver.ts
@@ -3,18 +3,19 @@
*
* This file is part of Puter.
*
- * Puter is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * Puter is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
*
* You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
+ * along with this program. If not, see
+ * [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
*/
import { Readable } from 'node:stream';
@@ -27,8 +28,8 @@ import { loadFileInput } from '../util/fileInput.js';
import { VOICE_CHANGER_COSTS } from './costs.js';
/**
- * Driver implementing `puter-speech2speech` — voice changer. Currently a
- * single provider (ElevenLabs).
+ * Driver implementing `puter-speech2speech` — voice changer. Currently a single
+ * provider (ElevenLabs).
*/
const DEFAULT_MODEL = 'eleven_multilingual_sts_v2';
@@ -57,7 +58,6 @@ interface ConvertArgs {
export class VoiceChangerDriver extends PuterDriver {
readonly driverInterface = 'puter-speech2speech';
- readonly noUserSession = true;
readonly driverName = 'elevenlabs-voice-changer';
readonly isDefault = true;
diff --git a/src/backend/drivers/ai-speech2txt/SpeechToTextDriver.ts b/src/backend/drivers/ai-speech2txt/SpeechToTextDriver.ts
index 88932377b..2a6f6b664 100644
--- a/src/backend/drivers/ai-speech2txt/SpeechToTextDriver.ts
+++ b/src/backend/drivers/ai-speech2txt/SpeechToTextDriver.ts
@@ -3,18 +3,19 @@
*
* This file is part of Puter.
*
- * Puter is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * Puter is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
*
* You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
+ * along with this program. If not, see
+ * [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
*/
import OpenAI, { toFile } from 'openai';
@@ -26,8 +27,8 @@ import { loadFileInput } from '../util/fileInput.js';
import { SPEECH_TO_TEXT_COSTS } from './costs.js';
/**
- * Driver implementing `puter-speech2txt`. Wraps OpenAI's audio API
- * (Whisper + GPT-4o transcribe models) for transcription and translation.
+ * Driver implementing `puter-speech2txt`. Wraps OpenAI's audio API (Whisper +
+ * GPT-4o transcribe models) for transcription and translation.
*
* `file` may be a path, uid/uuid ref, or data URL.
*/
@@ -100,7 +101,6 @@ interface TranscribeArgs {
export class SpeechToTextDriver extends PuterDriver {
readonly driverInterface = 'puter-speech2txt';
- readonly noUserSession = true;
readonly driverName = 'openai-speech2txt';
readonly isDefault = true;
diff --git a/src/backend/drivers/ai-speech2txt/XAISpeechToTextDriver.ts b/src/backend/drivers/ai-speech2txt/XAISpeechToTextDriver.ts
index 4ab09d05a..1e0e6640e 100644
--- a/src/backend/drivers/ai-speech2txt/XAISpeechToTextDriver.ts
+++ b/src/backend/drivers/ai-speech2txt/XAISpeechToTextDriver.ts
@@ -3,18 +3,19 @@
*
* This file is part of Puter.
*
- * Puter is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * Puter is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
*
* You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
+ * along with this program. If not, see
+ * [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
*/
import { Context } from '../../core/context.js';
@@ -26,10 +27,11 @@ import { loadFileInput } from '../util/fileInput.js';
/**
* Driver implementing `puter-speech2txt` for the xAI (Grok) STT API.
*
- * Uses the xAI /v1/stt REST endpoint which accepts multipart/form-data
- * with an audio file and returns a JSON transcript with word-level timestamps.
+ * Uses the xAI /v1/stt REST endpoint which accepts multipart/form-data with an
+ * audio file and returns a JSON transcript with word-level timestamps.
*
- * Pricing: $0.10/hr REST = 10 cents/hr = 10 * 1_000_000 / 3600 ≈ 2778 microcents/second
+ * Pricing: $0.10/hr REST = 10 cents/hr = 10 * 1_000_000 / 3600 ≈ 2778
+ * microcents/second
*/
const API_BASE = 'https://api.x.ai/v1';
@@ -66,7 +68,6 @@ interface TranscribeArgs {
export class XAISpeechToTextDriver extends PuterDriver {
readonly driverInterface = 'puter-speech2txt';
- readonly noUserSession = true;
readonly driverName = 'xai-speech2txt';
// Shared AI policy — see `drivers/util/aiLimits.ts` for the tier table.
diff --git a/src/backend/drivers/ai-tts/TTSDriver.ts b/src/backend/drivers/ai-tts/TTSDriver.ts
index 591045c48..0cb085985 100644
--- a/src/backend/drivers/ai-tts/TTSDriver.ts
+++ b/src/backend/drivers/ai-tts/TTSDriver.ts
@@ -3,18 +3,19 @@
*
* This file is part of Puter.
*
- * Puter is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * Puter is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
*
* You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
+ * along with this program. If not, see
+ * [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
*/
import { Context } from '../../core/context.js';
@@ -37,10 +38,9 @@ import type {
/**
* Driver implementing the `puter-tts` interface.
*
- * Manages multiple upstream TTS providers (OpenAI, ElevenLabs, AWS Polly)
- * and handles provider routing, voice/engine aggregation, and speech
- * synthesis. Each provider is an `ITTSProvider` instantiated from config
- * on boot.
+ * Manages multiple upstream TTS providers (OpenAI, ElevenLabs, AWS Polly) and
+ * handles provider routing, voice/engine aggregation, and speech synthesis.
+ * Each provider is an `ITTSProvider` instantiated from config on boot.
*/
// puter-js still routes TTS via legacy per-provider driver names rather
// than passing `{ provider }` in args, so alias the unified driver under
@@ -64,7 +64,6 @@ const ALIAS_TO_PROVIDER: Record = {
export class TTSDriver extends PuterDriver {
readonly driverInterface = 'puter-tts';
- readonly noUserSession = true;
readonly driverName = 'ai-tts';
readonly driverAliases = [...TTS_ALIASES];
readonly isDefault = true;
@@ -88,9 +87,7 @@ export class TTSDriver extends PuterDriver {
// -- Interface methods -------------------------------------------
- /**
- * List all available voices across all configured providers.
- */
+ /** List all available voices across all configured providers. */
async list_voices(args?: Record): Promise {
const provider =
(args?.provider as string | undefined) ?? this.#providerFromAlias();
@@ -109,9 +106,7 @@ export class TTSDriver extends PuterDriver {
return allVoices;
}
- /**
- * List all available engines/models across all configured providers.
- */
+ /** List all available engines/models across all configured providers. */
async list_engines(args?: Record): Promise {
const provider =
(args?.provider as string | undefined) ?? this.#providerFromAlias();
@@ -130,9 +125,7 @@ export class TTSDriver extends PuterDriver {
return allEngines;
}
- /**
- * List provider names that are currently configured.
- */
+ /** List provider names that are currently configured. */
async list(): Promise {
return Object.keys(this.#providers);
}
@@ -158,9 +151,8 @@ export class TTSDriver extends PuterDriver {
}
/**
- * Synthesize speech from text. Routes to the appropriate provider
- * based on the `provider` argument, or falls back to the first
- * available provider.
+ * Synthesize speech from text. Routes to the appropriate provider based on
+ * the `provider` argument, or falls back to the first available provider.
*/
async synthesize(
args: ISynthesizeArgs,
diff --git a/src/backend/drivers/ai-video/VideoGenerationDriver.ts b/src/backend/drivers/ai-video/VideoGenerationDriver.ts
index 1e1b38800..a4497dc09 100644
--- a/src/backend/drivers/ai-video/VideoGenerationDriver.ts
+++ b/src/backend/drivers/ai-video/VideoGenerationDriver.ts
@@ -3,18 +3,19 @@
*
* This file is part of Puter.
*
- * Puter is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * Puter is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
*
* You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
+ * along with this program. If not, see
+ * [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
*/
import { posix as pathPosix } from 'node:path';
@@ -48,7 +49,6 @@ const DEFAULT_PROVIDER = 'openai-video-generation';
*/
export class VideoGenerationDriver extends PuterDriver {
readonly driverInterface = 'puter-video-generation';
- readonly noUserSession = true;
readonly driverName = 'ai-video';
// puter-js's `txt2vid` can pass a provider id via `options.driver`, so
// alias all provider ids here. `generate` falls back to
diff --git a/src/backend/drivers/driverPolicies.test.ts b/src/backend/drivers/driverPolicies.test.ts
index d9cd237c0..66a97db66 100644
--- a/src/backend/drivers/driverPolicies.test.ts
+++ b/src/backend/drivers/driverPolicies.test.ts
@@ -151,11 +151,11 @@ describe.each([
expect(m.concurrent).toBe(AI_CONCURRENT);
});
- it('refuses bare account-session ("root") tokens', () => {
- // AI calls need a delegated credential — an app/worker token or a
- // dashboard-minted API token. `DriverController` enforces this off
- // the meta flag; dropping it silently reopens session-token AI use.
- expect(m.noUserSession).toBe(true);
+ it('accepts bare account-session ("root") tokens', () => {
+ // Privileged ("godmode") apps run on the user's own session token
+ // rather than an app token, so the AI drivers can't distinguish
+ // them from a browser session and have to admit both.
+ expect(m.noUserSession).toBe(false);
});
});
diff --git a/src/backend/drivers/meta.ts b/src/backend/drivers/meta.ts
index 8ed42c97e..0dae932b0 100644
--- a/src/backend/drivers/meta.ts
+++ b/src/backend/drivers/meta.ts
@@ -3,18 +3,19 @@
*
* This file is part of Puter.
*
- * Puter is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * Puter is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
*
* You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
+ * along with this program. If not, see
+ * [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
*/
import type { Readable } from 'node:stream';
@@ -78,24 +79,24 @@ export interface DriverRateLimitSpec {
/** Window length, in milliseconds. */
window: number;
/**
- * Per-subscription overrides for `limit`. Keyed by
- * `SubscriptionPolicy.id` (`user_free`, `temp_free`, `unlimited`,
- * etc.). Falls back to `limit` when the actor's subscription isn't
- * in the map. Same mechanic as `DriverConcurrentSpec.bySubscription`.
+ * Per-subscription overrides for `limit`. Keyed by `SubscriptionPolicy.id`
+ * (`user_free`, `temp_free`, `unlimited`, etc.). Falls back to `limit` when
+ * the actor's subscription isn't in the map. Same mechanic as
+ * `DriverConcurrentSpec.bySubscription`.
*/
bySubscription?: Record;
/**
- * Storage backend to count against. Omit to use the server-wide
- * default configured by `config.rate_limit.backend`.
+ * Storage backend to count against. Omit to use the server-wide default
+ * configured by `config.rate_limit.backend`.
*/
backend?: RateLimitBackend;
}
export interface DriverRateLimitConfig {
/**
- * Applied to any method not listed in `methods`. Lets a driver opt
- * the whole interface into tighter limits than the global driver
- * default without enumerating every method.
+ * Applied to any method not listed in `methods`. Lets a driver opt the
+ * whole interface into tighter limits than the global driver default
+ * without enumerating every method.
*/
default?: DriverRateLimitSpec;
/** Per-method overrides. Keys are driver method names. */
@@ -103,10 +104,9 @@ export interface DriverRateLimitConfig {
}
/**
- * Validate a `rateLimit` block declared by a driver. Throws on bad
- * shape so registration fails loudly at boot rather than silently
- * misconfiguring production traffic. Returns the value unchanged on
- * success for chaining.
+ * Validate a `rateLimit` block declared by a driver. Throws on bad shape so
+ * registration fails loudly at boot rather than silently misconfiguring
+ * production traffic. Returns the value unchanged on success for chaining.
*/
export function validateDriverRateLimit(
value: unknown,
@@ -183,9 +183,9 @@ function validateBySubscription(value: unknown, label: string): void {
}
/**
- * Resolve the spec that applies to a given method on a driver. Per-method
- * entry wins over `default`; returns `undefined` if neither is set so the
- * caller can apply its own fallback.
+ * Resolve the spec that applies to a given method on a driver. Per-method entry
+ * wins over `default`; returns `undefined` if neither is set so the caller can
+ * apply its own fallback.
*/
export function resolveDriverMethodRateLimit(
cfg: DriverRateLimitConfig | undefined,
@@ -205,15 +205,15 @@ export interface DriverConcurrentSpec {
/** Maximum simultaneous in-flight requests. */
limit: number;
/**
- * Per-subscription overrides keyed by `SubscriptionPolicy.id`
- * (`user_free`, `temp_free`, `unlimited`, etc.). Falls back to
- * `limit` when the actor's subscription isn't in the map.
+ * Per-subscription overrides keyed by `SubscriptionPolicy.id` (`user_free`,
+ * `temp_free`, `unlimited`, etc.). Falls back to `limit` when the actor's
+ * subscription isn't in the map.
*/
bySubscription?: Record;
/**
* Storage backend. Memory is per-process (use only on single-node
- * deployments); redis coordinates across nodes; kv is rarely the
- * right choice for concurrency but supported for parity.
+ * deployments); redis coordinates across nodes; kv is rarely the right
+ * choice for concurrency but supported for parity.
*/
backend?: RateLimitBackend;
}
@@ -226,8 +226,8 @@ export interface DriverConcurrentConfig {
}
/**
- * Validate a `concurrent` block. Mirrors `validateDriverRateLimit` —
- * throws with a labelled path so a malformed entry surfaces at boot.
+ * Validate a `concurrent` block. Mirrors `validateDriverRateLimit` — throws
+ * with a labelled path so a malformed entry surfaces at boot.
*/
export function validateDriverConcurrent(
value: unknown,
@@ -284,10 +284,10 @@ function validateConcurrentSpec(value: unknown, label: string): void {
}
/**
- * Resolve the concurrent spec for a given method on a driver. Same
- * precedence as `resolveDriverMethodRateLimit`: per-method wins over
- * `default`; `undefined` means no concurrency cap is declared, and the
- * caller should leave the method unbounded.
+ * Resolve the concurrent spec for a given method on a driver. Same precedence
+ * as `resolveDriverMethodRateLimit`: per-method wins over `default`;
+ * `undefined` means no concurrency cap is declared, and the caller should leave
+ * the method unbounded.
*/
export function resolveDriverMethodConcurrent(
cfg: DriverConcurrentConfig | undefined,
@@ -320,31 +320,30 @@ export interface DriverMeta {
/**
* Rate-limit policy for this driver. Per-method specs override the
* `default` spec; both are optional. `DriverController` consults this
- * before invoking the method and falls back to the global driver
- * default (600/min) if nothing is declared.
+ * before invoking the method and falls back to the global driver default
+ * (600/min) if nothing is declared.
*/
rateLimit?: DriverRateLimitConfig;
/**
- * Concurrent in-flight policy for this driver. When set, the
- * controller acquires a slot before invoking the method and releases
- * in `finally`. Absent → no concurrency cap (current behaviour).
+ * Concurrent in-flight policy for this driver. When set, the controller
+ * acquires a slot before invoking the method and releases in `finally`.
+ * Absent → no concurrency cap (current behaviour).
*/
concurrent?: DriverConcurrentConfig;
/**
- * When true, `/drivers/call` rejects bare account-session ("root")
- * tokens for this driver: callers must present an app/worker token or
- * a dashboard-minted API token. Per-driver counterpart of the
- * `noUserSession` route option — the dispatch route is shared, so the
- * flag lives on the driver. Set on the AI drivers so a copied session
- * token can't double as an AI credential.
+ * When true, `/drivers/call` rejects bare account-session ("root") tokens
+ * for this driver: callers must present an app/worker token or a
+ * dashboard-minted API token. Per-driver counterpart of the `noUserSession`
+ * route option — the dispatch route is shared, so the flag lives on the
+ * driver.
*/
noUserSession?: boolean;
}
/**
* Extract driver metadata from a driver instance. Checks decorator-set
- * prototype metadata first, then falls back to instance properties.
- * Returns `null` if the driver doesn't declare an interface.
+ * prototype metadata first, then falls back to instance properties. Returns
+ * `null` if the driver doesn't declare an interface.
*/
export function resolveDriverMeta(
driver: WithLifecycle & Record,
@@ -414,15 +413,15 @@ export function resolveDriverMeta(
/**
* Framework/lifecycle method names that must never be reachable via
- * `/drivers/call`. These live on `PuterDriver` (see `drivers/types.ts`) and
- * are the machinery the dispatch surface must exclude. For class-based
- * drivers a concrete `override` of one still carries the same name and is
- * caught here; for plain-object drivers (registered by extensions — see
- * `server.ts`, `typeof DriverClass === 'object'`) there is no base prototype
- * to distinguish them, so this denylist is the *only* thing keeping a
- * hook off the RPC surface. Any lifecycle hook added to `PuterDriver` must
- * be added here too — the per-driver guard test (`callableMethods.test.ts`)
- * fails loudly if a base method starts leaking into every driver's surface.
+ * `/drivers/call`. These live on `PuterDriver` (see `drivers/types.ts`) and are
+ * the machinery the dispatch surface must exclude. For class-based drivers a
+ * concrete `override` of one still carries the same name and is caught here;
+ * for plain-object drivers (registered by extensions — see `server.ts`, `typeof
+ * DriverClass === 'object'`) there is no base prototype to distinguish them, so
+ * this denylist is the _only_ thing keeping a hook off the RPC surface. Any
+ * lifecycle hook added to `PuterDriver` must be added here too — the per-driver
+ * guard test (`callableMethods.test.ts`) fails loudly if a base method starts
+ * leaking into every driver's surface.
*/
export const RESERVED_DRIVER_METHODS: ReadonlySet = new Set([
'onServerStart',
@@ -437,16 +436,16 @@ export const RESERVED_DRIVER_METHODS: ReadonlySet = new Set([
* The RPC surface is defined structurally rather than by a hand-maintained
* per-method allow-list. Walking from the instance up to (but not including)
* `Object.prototype`, a name is callable iff it resolves to a function and is
- * neither `constructor` nor a `RESERVED_DRIVER_METHODS` entry. This covers
- * both driver shapes the server accepts (`server.ts`): class instances (RPC
- * methods on the concrete prototype, config on the instance) and plain
- * objects (everything own, used verbatim by extensions). It excludes all
+ * neither `constructor` nor a `RESERVED_DRIVER_METHODS` entry. This covers both
+ * driver shapes the server accepts (`server.ts`): class instances (RPC methods
+ * on the concrete prototype, config on the instance) and plain objects
+ * (everything own, used verbatim by extensions). It excludes all
* `Object.prototype` members (`toString`, `valueOf`, `__proto__`, …), the
* `constructor`, and the lifecycle hooks.
*
* `#`-private helpers need no handling: they are not real property keys, so
* `getOwnPropertyNames` never lists them and `driver['#x']` is `undefined`.
- * Only *plain* public methods can appear here.
+ * Only _plain_ public methods can appear here.
*
* Getters are excluded (we read the descriptor's `.value`, never access the
* property), so evaluating this set never runs driver code. Intended to be
diff --git a/src/backend/testUtil.ts b/src/backend/testUtil.ts
index 8f760648d..a6cf752f2 100644
--- a/src/backend/testUtil.ts
+++ b/src/backend/testUtil.ts
@@ -76,13 +76,13 @@ export const createPgMockPostgresDatabaseClient = async (
/**
* When `PUTER_TEST_DB_ENGINE=postgres` is set, `setupTestServer` swaps its
- * default sqlite test database for an in-memory Postgres backed by pgmock
- * (with the bundled Postgres migrations applied on boot). Tests that
- * explicitly override `database` still win — the env var only affects the
- * implicit default used by callers that don't pass any DB overrides.
+ * default sqlite test database for an in-memory Postgres backed by pgmock (with
+ * the bundled Postgres migrations applied on boot). Tests that explicitly
+ * override `database` still win — the env var only affects the implicit default
+ * used by callers that don't pass any DB overrides.
*
- * Recognized values: `postgres` → pgmock. Anything else (including unset) →
- * the original sqlite-in-memory default.
+ * Recognized values: `postgres` → pgmock. Anything else (including unset) → the
+ * original sqlite-in-memory default.
*/
const testDatabaseDefault = (): IConfig['database'] => {
const engine = (process.env.PUTER_TEST_DB_ENGINE ?? '').toLowerCase();
@@ -105,10 +105,10 @@ export type SetupTestServerOptions = {
};
/**
- * Grab a free port by binding to 0 and releasing it. Done up-front (rather
- * than letting the server listen on 0) so the port is known while building
- * config — `origin` / `api_base_url` consumers like LocalWorkerService read
- * it at construction time.
+ * Grab a free port by binding to 0 and releasing it. Done up-front (rather than
+ * letting the server listen on 0) so the port is known while building config —
+ * `origin` / `api_base_url` consumers like LocalWorkerService read it at
+ * construction time.
*/
export const allocateEphemeralPort = (): Promise =>
new Promise((resolve, reject) => {
@@ -210,25 +210,25 @@ export type TestUserCredentials = {
password: string;
token: string;
/**
- * Full-access access token (what the dashboard's "API Token" flow
- * mints). AI surfaces reject bare session tokens (`noUserSession`),
- * so suites exercising them authenticate with this instead.
+ * Full-access access token (what the dashboard's "API Token" flow mints).
+ * The `/puterai/*` wire routes reject bare session tokens
+ * (`noUserSession`), so suites exercising them authenticate with this.
*/
apiToken: string;
/**
- * User-scoped worker session token (what deploying a worker with no
- * app binding mints — `kind='worker'` session row). Never treated as
- * a root token: suites use it to prove worker credentials pass the
- * `noUserSession` gates.
+ * User-scoped worker session token (what deploying a worker with no app
+ * binding mints — `kind='worker'` session row). Never treated as a root
+ * token: suites use it to prove worker credentials pass the `noUserSession`
+ * gates.
*/
workerToken: string;
};
/**
- * Seed a user with a known password directly through the stores (same steps
- * as DefaultUserService's admin bootstrap: bcrypt-hashed password, home
- * directory tree, optional admin-group membership) and mint a session token
- * the same way `POST /login` does.
+ * Seed a user with a known password directly through the stores (same steps as
+ * DefaultUserService's admin bootstrap: bcrypt-hashed password, home directory
+ * tree, optional admin-group membership) and mint a session token the same way
+ * `POST /login` does.
*/
export const createTestUser = async (
server: PuterServer,
@@ -292,20 +292,20 @@ export const createTestUser = async (
export type PuterTestEnv = {
/**
- * Root origin (`http://puter.localhost:`) — GUI and root-only
- * routes like `POST /login` live here.
+ * Root origin (`http://puter.localhost:`) — GUI and root-only routes
+ * like `POST /login` live here.
*/
origin: string;
/**
- * API origin (`http://api.puter.localhost:`) — what puter.js
- * clients use as their APIOrigin. Routes gated on the `api` subdomain
- * (e.g. `/whoami`) only match this host.
+ * API origin (`http://api.puter.localhost:`) — what puter.js clients
+ * use as their APIOrigin. Routes gated on the `api` subdomain (e.g.
+ * `/whoami`) only match this host.
*/
apiOrigin: string;
/**
- * Seeded accounts: an admin and two regular (non-privileged) users.
- * `other` exists so suites can exercise cross-user flows (permission
- * grants, access denials) without creating users on the fly.
+ * Seeded accounts: an admin and two regular (non-privileged) users. `other`
+ * exists so suites can exercise cross-user flows (permission grants, access
+ * denials) without creating users on the fly.
*/
users: {
admin: TestUserCredentials;
@@ -332,8 +332,8 @@ export const TEST_OTHER_USER_CREDENTIALS = {
/**
* Boot an in-memory Puter server on a real ephemeral port with deterministic
* credentials, for client test runners (puter.js on node, browsers, workerd).
- * Clients can authenticate with the pre-minted tokens or via a real
- * `POST /login` using the fixed passwords — no stdout scraping.
+ * Clients can authenticate with the pre-minted tokens or via a real `POST
+ * /login` using the fixed passwords — no stdout scraping.
*/
export const setupPuterTestEnv = async (
configOverrides?: IConfig,
diff --git a/src/puter-js/tests/api/suites/ai.suite.ts b/src/puter-js/tests/api/suites/ai.suite.ts
index ae15795fc..6347b2bc6 100644
--- a/src/puter-js/tests/api/suites/ai.suite.ts
+++ b/src/puter-js/tests/api/suites/ai.suite.ts
@@ -8,10 +8,10 @@ import { suite, type TestContext } from '../harness/types.ts';
*/
/**
- * The AI drivers reject bare session tokens (`noUserSession` driver meta):
- * programmatic AI callers must hold an app/worker token or a
- * dashboard-minted API token. Authenticate each AI test the way a real
- * caller would — with the full-access API token. The harness re-issues the
+ * The `/puterai/*` wire routes still require a delegated credential, so
+ * these tests authenticate the way a programmatic caller would — with the
+ * full-access API token. (The drivers themselves also take a plain session
+ * token; that path has its own test below.) The harness re-issues the
* session token to shared SDK instances between tests, so no restore is
* needed here.
*/
@@ -190,9 +190,10 @@ export default suite('ai', {
}
},
- 'a bare session token cannot call the AI driver': async (t) => {
- // No useApiToken here — the point is that the account session
- // ("root") token is rejected with guidance toward app/API tokens.
+ 'a bare session token can call the AI driver': async (t) => {
+ // No useApiToken here — privileged ("godmode") apps run on the
+ // user's own account session token, so the driver has to accept
+ // it. The `/puterai/*` wire routes still don't (see below).
const res = await fetch(`${t.env.apiOrigin}/drivers/call`, {
method: 'POST',
headers: {
@@ -209,22 +210,25 @@ export default suite('ai', {
},
}),
});
- t.assert.equal(res.status, 403, 'session token should be rejected');
const body = JSON.stringify(await res.json());
+ t.assert.equal(
+ res.status,
+ 200,
+ `session token should reach the driver, got ${res.status}: ${body}`,
+ );
t.assert.ok(
- body.includes('app_or_api_token_required'),
- `rejection should carry app_or_api_token_required, got ${body}`,
+ !body.includes('app_or_api_token_required'),
+ `session token must not be rejected by credential shape, got ${body}`,
);
},
- 'a worker token passes the AI credential gate': async (t) => {
+ 'a worker token can call the AI driver': async (t) => {
// Workers are never treated as root tokens. This uses a REAL
// user-scoped worker session token (minted the same way an
// app-less worker deployment mints one), so the whole middleware
// path is exercised: JWT → session row (kind='worker') → actor →
- // noUserSession gate. Calling the driver's `models` method keeps
- // this free of any AI inference — the gate rejects by credential
- // shape before the handler, so a 200 here proves admission.
+ // driver. Calling the driver's `models` method keeps this free of
+ // any AI inference.
const res = await fetch(`${t.env.apiOrigin}/drivers/call`, {
method: 'POST',
headers: {