diff --git a/eslint.config.js b/eslint.config.js
index 0bd44748..2ee7cabb 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -63,7 +63,7 @@ export default defineConfig([
'@stylistic/indent-binary-ops': ['error', 4],
'@stylistic/array-bracket-newline': ['error', 'consistent'],
'@stylistic/semi': ['error', 'always'],
- '@stylistic/quotes': ['error', 'single', { 'avoidEscape': true }],
+ '@stylistic/quotes': 'off',
'@stylistic/function-call-argument-newline': ['error', 'consistent'],
'@stylistic/arrow-spacing': ['error', { before: true, after: true }],
'@stylistic/space-before-function-paren': ['error', { 'anonymous': 'never', 'named': 'never', 'asyncArrow': 'always', 'catch': 'always' }],
@@ -115,7 +115,7 @@ export default defineConfig([
'@stylistic/indent-binary-ops': ['error', 4],
'@stylistic/array-bracket-newline': ['error', 'consistent'],
'@stylistic/semi': ['error', 'always'],
- '@stylistic/quotes': ['error', 'single', { 'avoidEscape': true }],
+ '@stylistic/quotes': ['error', 'single'],
'@stylistic/function-call-argument-newline': ['error', 'consistent'],
'@stylistic/arrow-spacing': ['error', { before: true, after: true }],
'@stylistic/space-before-function-paren': ['error', { 'anonymous': 'never', 'named': 'never', 'asyncArrow': 'always', 'catch': 'always' }],
@@ -166,7 +166,7 @@ export default defineConfig([
'@stylistic/indent-binary-ops': ['error', 4],
'@stylistic/array-bracket-newline': ['error', 'consistent'],
'@stylistic/semi': ['error', 'always'],
- '@stylistic/quotes': ['error', 'single', { 'avoidEscape': true }],
+ '@stylistic/quotes': ['error', 'single'],
'@stylistic/function-call-argument-newline': ['error', 'consistent'],
'@stylistic/arrow-spacing': ['error', { before: true, after: true }],
'@stylistic/space-before-function-paren': ['error', { 'anonymous': 'never', 'named': 'never', 'asyncArrow': 'always', 'catch': 'never' }],
@@ -212,7 +212,7 @@ export default defineConfig([
'@stylistic/indent-binary-ops': ['error', 4],
'@stylistic/array-bracket-newline': ['error', 'consistent'],
'@stylistic/semi': ['error', 'always'],
- '@stylistic/quotes': ['error', 'single', { 'avoidEscape': true }],
+ '@stylistic/quotes': ['error', 'single'],
'@stylistic/function-call-argument-newline': ['error', 'consistent'],
'@stylistic/arrow-spacing': ['error', { before: true, after: true }],
'@stylistic/space-before-function-paren': ['error', { 'anonymous': 'never', 'named': 'never', 'asyncArrow': 'always', 'catch': 'never' }],
diff --git a/src/backend/src/filesystem/ll_operations/ll_read.js b/src/backend/src/filesystem/ll_operations/ll_read.js
index e70354c9..680a63a9 100644
--- a/src/backend/src/filesystem/ll_operations/ll_read.js
+++ b/src/backend/src/filesystem/ll_operations/ll_read.js
@@ -16,148 +16,153 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-const APIError = require('../../api/APIError');
-const { get_user } = require('../../helpers');
-const { MemoryFSProvider } = require('../../modules/puterfs/customfs/MemoryFSProvider');
-const { UserActorType } = require('../../services/auth/Actor');
-const { Actor } = require('../../services/auth/Actor');
-const { DB_WRITE } = require('../../services/database/consts');
-const { Context } = require('../../util/context');
-const { buffer_to_stream } = require('../../util/streamutil');
-const { TYPE_SYMLINK, TYPE_DIRECTORY } = require('../FSNodeContext');
-const { LLFilesystemOperation } = require('./definitions');
+const APIError = require("../../api/APIError");
+const { Sequence } = require("../../codex/Sequence");
+const { MemoryFSProvider } = require("../../modules/puterfs/customfs/MemoryFSProvider");
-const checkACLForRead = async (aclService, actor, fsNode, skip = false) => {
- if ( skip ) {
- return;
- }
- if ( !await aclService.check(actor, fsNode, 'read') ) {
- throw await aclService.get_safe_acl_error(actor, fsNode, 'read');
- }
-};
-const typeCheckForRead = async (fsNode) => {
- if ( await fsNode.get('type') === TYPE_DIRECTORY ) {
- throw APIError.create('cannot_read_a_directory');
- }
-};
+const { DB_WRITE } = require("../../services/database/consts");
+const { buffer_to_stream } = require("../../util/streamutil");
+const { TYPE_SYMLINK, TYPE_DIRECTORY } = require("../FSNodeContext");
+const { LLFilesystemOperation } = require("./definitions");
+
+const dry_checks = [
+ async function check_ACL_for_read (a) {
+ if ( a.get('no_acl') ) return;
+ const context = a.iget('context');
+ const svc_acl = context.get('services').get('acl');
+ const { fsNode, actor } = a.values();
+ if ( ! await svc_acl.check(actor, fsNode, 'read') ) {
+ throw await svc_acl.get_safe_acl_error(actor, fsNode, 'read');
+ }
+ },
+ async function type_check_for_read (a) {
+ const fsNode = a.get('fsNode');
+ if ( await fsNode.get('type') === TYPE_DIRECTORY ) {
+ throw APIError.create('cannot_read_a_directory');
+ }
+ },
+];
class LLRead extends LLFilesystemOperation {
static CONCERN = 'filesystem';
- async _run({ fsNode, no_acl, actor, offset, length, range, version_id } = {}){
- // extract services from context
- const aclService = Context.get('services').get('acl');
- const db = Context.get('services')
- .get('database').get(DB_WRITE, 'filesystem');
- const fileCacheService = Context.get('services').get('file-cache');
-
- // validate input
- if ( !await fsNode.exists() ){
- throw APIError.create('subject_does_not_exist');
- }
- // validate initial node
- await checkACLForRead(aclService, actor, fsNode, no_acl);
- await typeCheckForRead(fsNode);
-
- let type = await fsNode.get('type');
- while ( type === TYPE_SYMLINK ) {
- fsNode = await fsNode.getTarget();
- type = await fsNode.get('type');
- }
-
- // validate symlink leaf node
- await checkACLForRead(aclService, actor, fsNode);
- await typeCheckForRead(fsNode);
-
- // calculate range inputs
- const has_range = (
- offset !== undefined &&
- offset !== 0
- ) || (
- length !== undefined &&
- length != await fsNode.get('size')
- ) || range !== undefined;
-
- // timestamp access
- await db.write('UPDATE `fsentries` SET `accessed` = ? WHERE `id` = ?',
- [Date.now() / 1000, fsNode.mysql_id]);
-
- const ownerId = await fsNode.get('user_id');
- const ownerActor = new Actor({
- type: new UserActorType({
- user: await get_user({ id: ownerId }),
- }),
- });
-
- //define metering service
-
- /** @type {import("../../services/MeteringService/MeteringService").MeteringAndBillingService} */
- const meteringService = Context.get('services').get('meteringService').meteringAndBillingService;
- // check file cache
- const maybe_buffer = await fileCacheService.try_get(fsNode); // TODO DS: do we need those cache hit logs?
- if ( maybe_buffer ) {
- // Meter cached egress
- // return cached stream
- if ( has_range && (length || offset) ) {
- meteringService.incrementUsage(ownerActor, 'filesystem:cached-egress:bytes', length);
- return buffer_to_stream(maybe_buffer.slice(offset, offset + length));
+ static METHODS = {
+ _run: new Sequence({
+ async before_each (a, step) {
+ const operation = a.iget();
+ operation.checkpoint('step:' + step.name);
}
- meteringService.incrementUsage(ownerActor, 'filesystem:cached-egress:bytes', await fsNode.get('size'));
- return buffer_to_stream(maybe_buffer);
- }
-
- // if no cache attempt reading from storageProvider (s3)
- const svc_mountpoint = Context.get('services').get('mountpoint');
- const provider = await svc_mountpoint.get_provider(fsNode.selector);
- const storage = svc_mountpoint.get_storage(provider.constructor.name);
-
- // Empty object here is in the case of local fiesystem,
- // where s3:location will return null.
- // TODO: storage interface shouldn't have S3-specific properties.
- const location = await fsNode.get('s3:location') ?? {};
- const stream = (await storage.create_read_stream(await fsNode.get('uid'), {
- // TODO: fs:decouple-s3
- bucket: location.bucket,
- bucket_region: location.bucket_region,
- version_id,
- key: location.key,
- memory_file: fsNode.entry,
- ...(range ? { range } : (has_range ? {
- range: `bytes=${offset}-${offset + length - 1}`,
- } : {})),
- }));
-
- // Meter ingress
- const size = await (async () => {
- if ( range ){
- const match = range.match(/bytes=(\d+)-(\d+)/);
- if ( match ) {
- const start = parseInt(match[1], 10);
- const end = parseInt(match[2], 10);
- return end - start + 1;
+ }, [
+ async function check_that_node_exists (a) {
+ if ( ! await a.get('fsNode').exists() ) {
+ throw APIError.create('subject_does_not_exist');
}
- }
- if ( has_range ) {
- return length;
- }
- return await fsNode.get('size');
- })();
- meteringService.incrementUsage(ownerActor, 'filesystem:egress:bytes', size);
-
- // cache if whole file read
- if ( !has_range ) {
- // only cache for non-memoryfs providers
- if ( ! (fsNode.provider instanceof MemoryFSProvider) ) {
- const res = await fileCacheService.maybe_store(fsNode, stream);
- if ( res.stream ) {
- // return with split cached stream
- return res.stream;
+ },
+ ...dry_checks,
+ async function resolve_symlink (a) {
+ let fsNode = a.get('fsNode');
+ let type = await fsNode.get('type');
+ while ( type === TYPE_SYMLINK ) {
+ fsNode = await fsNode.getTarget();
+ type = await fsNode.get('type');
}
- }
- }
- return stream;
- }
+ a.set('fsNode', fsNode);
+ },
+ ...dry_checks,
+ async function calculate_has_range (a) {
+ const { offset, length, range } = a.values();
+ const fsNode = a.get('fsNode');
+ const has_range = (
+ offset !== undefined &&
+ offset !== 0
+ ) || (
+ length !== undefined &&
+ length != await fsNode.get('size')
+ ) || range !== undefined;
+ a.set('has_range', has_range);
+ },
+ async function update_accessed (a) {
+ const context = a.iget('context');
+ const db = context.get('services')
+ .get('database').get(DB_WRITE, 'filesystem');
+
+ const fsNode = a.get('fsNode');
+
+ await db.write(
+ 'UPDATE `fsentries` SET `accessed` = ? WHERE `id` = ?',
+ [Date.now()/1000, fsNode.mysql_id]
+ );
+ },
+ async function check_for_cached_copy (a) {
+ const context = a.iget('context');
+ const svc_fileCache = context.get('services').get('file-cache');
+
+ const { fsNode, offset, length } = a.values();
+
+ const maybe_buffer = await svc_fileCache.try_get(fsNode, a.log);
+ if ( maybe_buffer ) {
+ a.log.cache(true, 'll_read');
+ const { has_range } = a.values();
+ if ( has_range && (length || offset) ) {
+ return a.stop(
+ buffer_to_stream(maybe_buffer.slice(offset, offset+length))
+ );
+ }
+ return a.stop(
+ buffer_to_stream(maybe_buffer)
+ );
+ }
+
+ a.log.cache(false, 'll_read');
+ },
+ async function create_S3_read_stream (a) {
+ const context = a.iget('context');
+
+ const { fsNode, version_id, offset, length, has_range, range } = a.values();
+
+ const svc_mountpoint = context.get('services').get('mountpoint');
+ const provider = await svc_mountpoint.get_provider(fsNode.selector);
+ const storage = svc_mountpoint.get_storage(provider.constructor.name);
+
+ // Empty object here is in the case of local fiesystem,
+ // where s3:location will return null.
+ // TODO: storage interface shouldn't have S3-specific properties.
+ const location = await fsNode.get('s3:location') ?? {};
+
+ const stream = (await storage.create_read_stream(await fsNode.get('uid'), {
+ // TODO: fs:decouple-s3
+ bucket: location.bucket,
+ bucket_region: location.bucket_region,
+ version_id,
+ key: location.key,
+ memory_file: fsNode.entry,
+ ...(range? {range} : (has_range ? {
+ range: `bytes=${offset}-${offset+length-1}`
+ } : {})),
+ }));
+
+ a.set('stream', stream);
+ },
+ async function store_in_cache (a) {
+ const context = a.iget('context');
+ const svc_fileCache = context.get('services').get('file-cache');
+
+ const { fsNode, stream, has_range, range} = a.values();
+
+ if ( ! has_range ) {
+ // only cache for non-memoryfs providers
+ if ( ! (fsNode.provider instanceof MemoryFSProvider) ) {
+ const res = await svc_fileCache.maybe_store(fsNode, stream);
+ if ( res.stream ) a.set('stream', res.stream);
+ }
+ }
+ },
+ async function return_stream (a) {
+ return a.get('stream');
+ },
+ ]),
+ };
}
module.exports = {
- LLRead,
+ LLRead
};
diff --git a/src/backend/src/modules/puterfs/MountpointService.js b/src/backend/src/modules/puterfs/MountpointService.js
index 81bc3193..cfe5f61a 100644
--- a/src/backend/src/modules/puterfs/MountpointService.js
+++ b/src/backend/src/modules/puterfs/MountpointService.js
@@ -17,13 +17,15 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-const { RootNodeSelector, NodeUIDSelector, NodeChildSelector, NodePathSelector, try_infer_attributes } = require("../../filesystem/node/selectors");
+// const Mountpoint = o => ({ ...o });
+
+const { RootNodeSelector, NodeUIDSelector, NodeChildSelector, NodePathSelector, NodeInternalIDSelector, NodeSelector, try_infer_attributes } = require("../../filesystem/node/selectors");
const BaseService = require("../../services/BaseService");
/**
* This will eventually be a service which manages the storage
* backends for mountpoints.
- *
+ *
* For the moment, this is a way to access the storage backend
* in situations where ContextInitService isn't able to
* initialize a context.
@@ -38,16 +40,29 @@ const BaseService = require("../../services/BaseService");
* and their associated storage backends in future implementations.
*/
class MountpointService extends BaseService {
-
- #storage = {};
- #mounters = {};
- #mountpoints = {};
-
- register_mounter(name, mounter) {
- this.#mounters[name] = mounter;
+ _construct () {
+ this.mounters_ = {};
+ this.mountpoints_ = {};
}
- async ['__on_boot.consolidation']() {
+ register_mounter (name, mounter) {
+ this.mounters_[name] = mounter;
+ }
+
+ /**
+ * Initializes the MountpointService instance
+ * Sets up initial state with null storage backend
+ * @private
+ * @async
+ * @returns {Promise}
+ */
+ async _init () {
+ // key: name of provider class (e.g: "PuterFSProvider", "MemoryFSProvider")
+ // value: storage instance
+ this.storage_ = {};
+ }
+
+ async ['__on_boot.consolidation'] () {
const mountpoints = this.config.mountpoints ?? {
'/': {
mounter: 'puterfs',
@@ -57,38 +72,38 @@ class MountpointService extends BaseService {
for ( const path of Object.keys(mountpoints) ) {
const { mounter: mounter_name, options } =
mountpoints[path];
- const mounter = this.#mounters[mounter_name];
+ const mounter = this.mounters_[mounter_name];
const provider = await mounter.mount({
path,
- options,
+ options
});
- this.#mountpoints[path] = {
+ this.mountpoints_[path] = {
provider,
};
}
this.services.emit('filesystem.ready', {
- mountpoints: Object.keys(this.#mountpoints),
+ mountpoints: Object.keys(this.mountpoints_),
});
}
-
- async get_provider(selector) {
+
+ async get_provider (selector) {
// If there is only one provider, we don't need to do any of this,
// and that's a big deal because the current implementation requires
// fetching a filesystem entry before we even have operation-level
// transient memoization instantiated.
- if ( Object.keys(this.#mountpoints).length === 1 ) {
- return Object.values(this.#mountpoints)[0].provider;
+ if ( Object.keys(this.mountpoints_).length === 1 ) {
+ return Object.values(this.mountpoints_)[0].provider;
}
-
+
try_infer_attributes(selector);
if ( selector instanceof RootNodeSelector ) {
- return this.#mountpoints['/'].provider;
+ return this.mountpoints_['/'].provider;
}
if ( selector instanceof NodeUIDSelector ) {
- for ( const { provider } of Object.values(this.#mountpoints) ) {
+ for ( const [path, { provider }] of Object.entries(this.mountpoints_) ) {
const result = await provider.quick_check({
selector,
});
@@ -113,7 +128,7 @@ class MountpointService extends BaseService {
selector.setPropertiesKnownBySelector(probe);
if ( probe.path ) {
let longest_mount_path = '';
- for ( const path of Object.keys(this.#mountpoints) ) {
+ for ( const path of Object.keys(this.mountpoints_) ) {
if ( ! probe.path.startsWith(path) ) {
continue;
}
@@ -123,25 +138,25 @@ class MountpointService extends BaseService {
}
if ( longest_mount_path ) {
- return this.#mountpoints[longest_mount_path].provider;
+ return this.mountpoints_[longest_mount_path].provider;
}
}
// Use root mountpoint as fallback
- return this.#mountpoints['/'].provider;
+ return this.mountpoints_['/'].provider;
}
-
+
// Temporary solution - we'll develop this incrementally
- set_storage(provider, storage) {
- this.#storage[provider] = storage;
+ set_storage (provider, storage) {
+ this.storage_[provider] = storage;
}
/**
* Gets the current storage backend instance
* @returns {Object} The storage backend instance
*/
- get_storage(provider) {
- const storage = this.#storage[provider];
+ get_storage (provider) {
+ const storage = this.storage_[provider];
if ( ! storage ) {
throw new Error(`MountpointService.get_storage: storage for provider "${provider}" not found`);
}
diff --git a/src/backend/src/modules/puterfs/lib/PuterFSProvider.js b/src/backend/src/modules/puterfs/lib/PuterFSProvider.js
index afaf9365..40712ddc 100644
--- a/src/backend/src/modules/puterfs/lib/PuterFSProvider.js
+++ b/src/backend/src/modules/puterfs/lib/PuterFSProvider.js
@@ -1,18 +1,18 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
- *
+ *
* 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.
- *
+ *
* 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 .
*/
@@ -20,48 +20,43 @@
const putility = require('@heyputer/putility');
const { MultiDetachable } = putility.libs.listener;
const { TDetachable } = putility.traits;
-const { NodeInternalIDSelector, NodeChildSelector, NodeUIDSelector } = require('../../../filesystem/node/selectors');
-const { Context } = require('../../../util/context');
+const { TeePromise } = putility.libs.promise;
+
+const { NodeInternalIDSelector, NodeChildSelector, NodeUIDSelector, RootNodeSelector, NodePathSelector, NodeSelector } = require("../../../filesystem/node/selectors");
+const { Context } = require("../../../util/context");
const fsCapabilities = require('../../../filesystem/definitions/capabilities');
const { UploadProgressTracker } = require('../../../filesystem/storage/UploadProgressTracker');
const FSNodeContext = require('../../../filesystem/FSNodeContext');
const { RESOURCE_STATUS_PENDING_CREATE } = require('../ResourceService');
const { ParallelTasks } = require('../../../util/otelutil');
+
const { TYPE_DIRECTORY } = require('../../../filesystem/FSNodeContext');
const APIError = require('../../../api/APIError');
const { MODE_WRITE } = require('../../../services/fs/FSLockService');
const { DB_WRITE } = require('../../../services/database/consts');
const { stuck_detector_stream, hashing_stream } = require('../../../util/streamutil');
+
const crypto = require('crypto');
const { OperationFrame } = require('../../../services/OperationTraceService');
-const path = require('path');
-const uuidv4 = require('uuid').v4;
-const config = require('../../../config.js');
-const { Actor } = require('../../../services/auth/Actor.js');
-const { UserActorType } = require('../../../services/auth/Actor.js');
-const { get_user } = require('../../../helpers.js');
const STUCK_STATUS_TIMEOUT = 10 * 1000;
const STUCK_ALARM_TIMEOUT = 20 * 1000;
class PuterFSProvider extends putility.AdvancedBase {
+ static MODULES = {
+ _path: require('path'),
+ uuidv4: require('uuid').v4,
+ config: require('../../../config.js'),
+ };
- get #services() { // we really should just pass services in constructor, global state is a bit messy
- return Context.get('services');
- }
-
- /** @type {import('../../../services/MeteringService/MeteringService.js').MeteringAndBillingService} */
- get #meteringService() {
- return this.#services.get('meteringService').meteringAndBillingService;
- }
-
- constructor(...a) {
+ constructor (...a) {
super(...a);
- this.log_fsentriesNotFound = (config.logging ?? [])
+
+ this.log_fsentriesNotFound = (this.modules.config.logging ?? [])
.includes('fsentries-not-found');
}
- get_capabilities() {
+ get_capabilities () {
return new Set([
fsCapabilities.THUMBNAIL,
fsCapabilities.UUID,
@@ -80,16 +75,16 @@ class PuterFSProvider extends putility.AdvancedBase {
/**
* Check if a given node exists.
- *
+ *
* @param {Object} param
* @param {NodeSelector} param.selector - The selector used for checking.
* @returns {Promise} - True if the node exists, false otherwise.
*/
- async quick_check({
+ async quick_check ({
selector,
}) {
// a wrapper that access underlying database directly
- const fsEntryFetcher = this.#services.get('fsEntryFetcher');
+ const fsEntryFetcher = Context.get('services').get('fsEntryFetcher');
// shortcut: has full path
if ( selector?.path ) {
@@ -105,14 +100,18 @@ class PuterFSProvider extends putility.AdvancedBase {
// shortcut: parent uid + child name
if ( selector instanceof NodeChildSelector && selector.parent instanceof NodeUIDSelector ) {
- return await fsEntryFetcher.nameExistsUnderParent(selector.parent.uid,
- selector.name);
+ return await fsEntryFetcher.nameExistsUnderParent(
+ selector.parent.uid,
+ selector.name,
+ );
}
// shortcut: parent id + child name
if ( selector instanceof NodeChildSelector && selector.parent instanceof NodeInternalIDSelector ) {
- return await fsEntryFetcher.nameExistsUnderParentID(selector.parent.id,
- selector.name);
+ return await fsEntryFetcher.nameExistsUnderParentID(
+ selector.parent.id,
+ selector.name,
+ );
}
// TODO (xiaochen): we should fallback to stat but we cannot at this moment
@@ -120,7 +119,7 @@ class PuterFSProvider extends putility.AdvancedBase {
return false;
}
- async stat({
+ async stat ({
selector,
options,
controls,
@@ -135,7 +134,7 @@ class PuterFSProvider extends putility.AdvancedBase {
fsEntryService,
fsEntryFetcher,
resourceService,
- } = this.#services.values;
+ } = Context.get('services').values;
if ( options.tracer == null ) {
options.tracer = traceService.tracer;
@@ -152,10 +151,10 @@ class PuterFSProvider extends putility.AdvancedBase {
await new Promise (rslv => {
const detachables = new MultiDetachable();
- const callback = (_resolver) => {
+ const callback = (resolver) => {
detachables.as(TDetachable).detach();
rslv();
- };
+ }
// either the resource is free
{
@@ -163,7 +162,9 @@ class PuterFSProvider extends putility.AdvancedBase {
// Promise that will be resolved when the resource
// is free no matter what, and then it will be
// garbage collected.
- resourceService.waitForResource(selector).then(callback.bind(null, 'resourceService'));
+ resourceService.waitForResource(
+ selector
+ ).then(callback.bind(null, 'resourceService'));
}
// or pending information about the resource
@@ -175,7 +176,8 @@ class PuterFSProvider extends putility.AdvancedBase {
// is guaranteed to resolve eventually, and then this
// detachable will be detached by `callback` so the
// listener can be garbage collected.
- const det = fsEntryService.waitForEntry(node, callback.bind(null, 'fsEntryService'));
+ const det = fsEntryService.waitForEntry(
+ node, callback.bind(null, 'fsEntryService'));
if ( det ) detachables.add(det);
}
});
@@ -185,7 +187,8 @@ class PuterFSProvider extends putility.AdvancedBase {
entry = await fsEntryService.get(maybe_uid, options);
controls.log.debug('got an entry from the future');
} else {
- entry = await fsEntryFetcher.find(selector, options);
+ entry = await fsEntryFetcher.find(
+ selector, options);
}
if ( ! entry ) {
@@ -199,33 +202,38 @@ class PuterFSProvider extends putility.AdvancedBase {
}
if ( entry.id ) {
- controls.provide_selector(new NodeInternalIDSelector('mysql', entry.id, {
- source: 'FSNodeContext optimization',
- }));
+ controls.provide_selector(
+ new NodeInternalIDSelector('mysql', entry.id, {
+ source: 'FSNodeContext optimization'
+ })
+ );
}
return entry;
}
- async readdir({ node }) {
+ async readdir ({ context, node }) {
const uuid = await node.get('uid');
- const svc_fsentry = this.#services.get('fsEntryService');
+ const services = context.get('services');
+ const svc_fsentry = services.get('fsEntryService');
const child_uuids = await svc_fsentry
.fast_get_direct_descendants(uuid);
return child_uuids;
}
- async move({ context, node, new_parent, new_name, metadata }) {
+ async move ({ context, node, new_parent, new_name, metadata }) {
+ const { _path } = this.modules;
+ const services = context.get('services');
const old_path = await node.get('path');
- const new_path = path.join(await new_parent.get('path'), new_name);
+ const new_path = _path.join(await new_parent.get('path'), new_name);
- const svc_fsEntry = this.#services.get('fsEntryService');
+ const svc_fsEntry = services.get('fsEntryService');
const op_update = await svc_fsEntry.update(node.uid, {
...(
await node.get('parent_uid') !== await new_parent.get('uid')
- ? { parent_uid: await new_parent.get('uid') }
- : {}
+ ? { parent_uid: await new_parent.get('uid') }
+ : {}
),
path: new_path,
name: new_name,
@@ -242,10 +250,10 @@ class PuterFSProvider extends putility.AdvancedBase {
await op_update.awaitDone();
- const svc_fs = this.#services.get('filesystem');
+ const svc_fs = services.get('filesystem');
await svc_fs.update_child_paths(old_path, node.entry.path, user_id);
- const svc_event = this.#services.get('event');
+ const svc_event = services.get('event');
const promises = [];
promises.push(svc_event.emit('fs.move.file', {
@@ -261,17 +269,22 @@ class PuterFSProvider extends putility.AdvancedBase {
return node;
}
- async copy_tree({ context, source, parent, target_name }) {
- return await this.#copy_tree({ context, source, parent, target_name });
+ async copy_tree ({ context, source, parent, target_name }) {
+ return await this.copy_tree_(
+ { context, source, parent, target_name });
}
- async #copy_tree({ context, source, parent, target_name }) {
+ async copy_tree_ ({ context, source, parent, target_name }) {
+ // Modules
+ const { _path, uuidv4 } = this.modules;
+
// Services
- const svc_event = this.#services.get('event');
- const svc_trace = this.#services.get('traceService');
- const svc_size = this.#services.get('sizeService');
- const svc_resource = this.#services.get('resourceService');
- const svc_fsEntry = this.#services.get('fsEntryService');
- const svc_fs = this.#services.get('filesystem');
+ const services = context.get('services');
+ const svc_event = services.get('event');
+ const svc_trace = services.get('traceService');
+ const svc_size = services.get('sizeService');
+ const svc_resource = services.get('resourceService');
+ const svc_fsEntry = services.get('fsEntryService');
+ const svc_fs = services.get('filesystem');
// Context
const actor = Context.get('actor');
@@ -279,7 +292,7 @@ class PuterFSProvider extends putility.AdvancedBase {
const tracer = svc_trace.tracer;
const uuid = uuidv4();
- const timestamp = Math.round(Date.now() / 1000);
+ const ts = Math.round(Date.now()/1000);
await parent.fetchEntry();
await source.fetchEntry({ thumbnail: true });
@@ -290,13 +303,13 @@ class PuterFSProvider extends putility.AdvancedBase {
...(source.entry.is_shortcut ? {
is_shortcut: source.entry.is_shortcut,
shortcut_to: source.entry.shortcut_to,
- } : {}),
+ } :{}),
parent_uid: parent.uid,
name: target_name,
- created: timestamp,
- modified: timestamp,
+ created: ts,
+ modified: ts,
- path: path.join(await parent.get('path'), target_name),
+ path: _path.join(await parent.get('path'), target_name),
// if property exists but the value is undefined,
// it will still be included in the INSERT, causing
@@ -310,7 +323,7 @@ class PuterFSProvider extends putility.AdvancedBase {
svc_event.emit('fs.pending.file', {
fsentry: FSNodeContext.sanitize_pending_entry_info(raw_fsentry),
context: context,
- });
+ })
if ( await source.get('has-s3') ) {
Object.assign(raw_fsentry, {
@@ -320,7 +333,7 @@ class PuterFSProvider extends putility.AdvancedBase {
bucket_region: source.entry.bucket_region,
});
- await tracer.startActiveSpan('fs:cp:storage-copy', async span => {
+ await tracer.startActiveSpan(`fs:cp:storage-copy`, async span => {
let progress_tracker = new UploadProgressTracker();
svc_event.emit('fs.storage.progress.copy', {
@@ -329,7 +342,7 @@ class PuterFSProvider extends putility.AdvancedBase {
meta: {
item_uid: uuid,
item_path: raw_fsentry.path,
- },
+ }
});
// const storage = new PuterS3StorageStrategy({ services: svc });
@@ -363,19 +376,27 @@ class PuterFSProvider extends putility.AdvancedBase {
let node;
const tasks = new ParallelTasks({ tracer, max: 4 });
- await context.arun('fs:cp:parallel-portion', async () => {
+ await context.arun(`fs:cp:parallel-portion`, async () => {
// Add child copy tasks if this is a directory
if ( source.entry.is_dir ) {
- const children = await svc_fsEntry.fast_get_direct_descendants(source.uid);
+ const children = await svc_fsEntry.fast_get_direct_descendants(
+ source.uid
+ );
for ( const child_uuid of children ) {
- tasks.add('fs:cp:copy-child', async () => {
- const child_node = await svc_fs.node(new NodeUIDSelector(child_uuid));
+ tasks.add(`fs:cp:copy-child`, async () => {
+ const child_node = await svc_fs.node(
+ new NodeUIDSelector(child_uuid)
+ );
const child_name = await child_node.get('name');
// TODO: this should be LLCopy instead
- await this.#copy_tree({
+ await this.copy_tree_({
context,
- source: await svc_fs.node(new NodeUIDSelector(child_uuid)),
- parent: await svc_fs.node(new NodeUIDSelector(uuid)),
+ source: await svc_fs.node(
+ new NodeUIDSelector(child_uuid)
+ ),
+ parent: await svc_fs.node(
+ new NodeUIDSelector(uuid)
+ ),
target_name: child_name,
});
});
@@ -383,7 +404,7 @@ class PuterFSProvider extends putility.AdvancedBase {
}
// Add task to await entry
- tasks.add('fs:cp:entry-op', async () => {
+ tasks.add(`fs:cp:entry-op`, async () => {
await entryOp.awaitDone();
svc_resource.free(uuid);
const copy_fsNode = await svc_fs.node(new NodeUIDSelector(uuid));
@@ -396,7 +417,7 @@ class PuterFSProvider extends putility.AdvancedBase {
svc_event.emit('fs.create.file', {
node,
context,
- });
+ })
}, { force: true });
await tasks.awaitAll();
@@ -408,70 +429,66 @@ class PuterFSProvider extends putility.AdvancedBase {
return node;
}
- async unlink({ context, node }) {
+ async unlink ({ context, node }) {
if ( await node.get('type') === TYPE_DIRECTORY ) {
- console.log(`\x1B[31;1m===N=====${await node.get('path')}=========\x1B[0m`);
+ console.log(`\x1B[31;1m===N=====${await node.get('path')}=========\x1B[0m`)
throw new APIError(409, 'Cannot unlink a directory.');
}
- await this.#rmnode({ context, node });
+ await this.rmnode_({ context, node });
}
- async rmdir({ context, node, options = {} }) {
+ async rmdir ({ context, node, options = {} }) {
if ( await node.get('type') !== TYPE_DIRECTORY ) {
- console.log(`\x1B[31;1m===D1====${await node.get('path')}=========\x1B[0m`);
+ console.log(`\x1B[31;1m===D1====${await node.get('path')}=========\x1B[0m`)
throw new APIError(409, 'Cannot rmdir a file.');
}
if ( await node.get('immutable') ) {
- console.log(`\x1B[31;1m===D2====${await node.get('path')}=========\x1B[0m`);
+ console.log(`\x1B[31;1m===D2====${await node.get('path')}=========\x1B[0m`)
throw APIError.create('immutable');
}
// Services
- const svc_fsEntry = this.#services.get('fsEntryService');
+ const services = context.get('services');
+ const svc_fsEntry = services.get('fsEntryService');
- const children = await svc_fsEntry.fast_get_direct_descendants(await node.get('uid'));
+ const children = await svc_fsEntry.fast_get_direct_descendants(
+ await node.get('uid')
+ );
if ( children.length > 0 && ! options.ignore_not_empty ) {
- console.log(`\x1B[31;1m===D3====${await node.get('path')}=========\x1B[0m`);
+ console.log(`\x1B[31;1m===D3====${await node.get('path')}=========\x1B[0m`)
throw APIError.create('not_empty');
}
- await this.#rmnode({ context, node, options });
+ await this.rmnode_({ context, node, options });
}
- async #rmnode({ node, options: _options }) {
+ async rmnode_ ({ context, node, options }) {
// Services
- const svc_size = this.#services.get('sizeService');
- const svc_fsEntry = this.#services.get('fsEntryService');
+ const services = context.get('services');
+ const svc_size = services.get('sizeService');
+ const svc_fsEntry = services.get('fsEntryService');
if ( await node.get('immutable') ) {
throw new APIError(403, 'File is immutable.');
}
- const userId = await node.get('user_id');
- const fileSize = await node.get('size');
- svc_size.change_usage(userId,
- -1 * fileSize);
+ svc_size.change_usage(
+ await node.get('user_id'),
+ -1 * await node.get('size')
+ );
- const ownerActor = new Actor({
- type: new UserActorType({
- user: await get_user({ id: userId }),
- }),
- });
-
- this.#meteringService.incrementUsage(ownerActor, 'filesystem:delete:bytes', fileSize);
-
- const tracer = this.#services.get('traceService').tracer;
+ const tracer = services.get('traceService').tracer;
const tasks = new ParallelTasks({ tracer, max: 4 });
- tasks.add('remove-fsentry', async () => {
+ tasks.add(`remove-fsentry`, async () => {
await svc_fsEntry.delete(await node.get('uid'));
});
if ( await node.get('has-s3') ) {
- tasks.add('remove-from-s3', async () => {
+ tasks.add(`remove-from-s3`, async () => {
// const storage = new PuterS3StorageStrategy({ services: svc });
const storage = Context.get('storage');
const state_delete = storage.create_delete();
@@ -486,7 +503,7 @@ class PuterFSProvider extends putility.AdvancedBase {
/**
* Create a new directory.
- *
+ *
* @param {Object} param
* @param {Context} param.context
* @param {FSNode} param.parent
@@ -494,35 +511,41 @@ class PuterFSProvider extends putility.AdvancedBase {
* @param {boolean} param.immutable
* @returns {Promise}
*/
- async mkdir({ context, parent, name, immutable }) {
+ async mkdir({ context, parent, name, immutable}) {
const { actor, thumbnail } = context.values;
- const svc_fslock = this.#services.get('fslock');
- const lock_handle = await svc_fslock.lock_child(await parent.get('path'),
- name,
- MODE_WRITE);
+ const svc_fslock = context.get('services').get('fslock');
+ const lock_handle = await svc_fslock.lock_child(
+ await parent.get('path'),
+ name,
+ MODE_WRITE,
+ );
try {
+ const { _path, uuidv4 } = this.modules;
+
const ts = Math.round(Date.now() / 1000);
const uid = uuidv4();
- const resourceService = this.#services.get('resourceService');
- const svc_fsEntry = this.#services.get('fsEntryService');
- const svc_event = this.#services.get('event');
- const fs = this.#services.get('filesystem');
+ const resourceService = context.get('services').get('resourceService');
+ const svc_fsEntry = context.get('services').get('fsEntryService');
+ const svc_event = context.get('services').get('event');
+ const fs = context.get('services').get('filesystem');
- const existing = await fs.node(new NodeChildSelector(parent.selector, name));
+ const existing = await fs.node(
+ new NodeChildSelector(parent.selector, name)
+ );
- if ( await existing.exists() ) {
+ if (await existing.exists()) {
throw APIError.create('item_with_same_name_exists', null, {
entry_name: name,
});
}
- const svc_acl = this.#services.get('acl');
- if ( ! await parent.exists() ) {
+ const svc_acl = context.get('services').get('acl');
+ if (! await parent.exists()) {
throw APIError.create('subject_does_not_exist');
}
- if ( ! await svc_acl.check(actor, parent, 'write') ) {
+ if (! await svc_acl.check(actor, parent, 'write')) {
throw await svc_acl.get_safe_acl_error(actor, parent, 'write');
}
@@ -535,7 +558,7 @@ class PuterFSProvider extends putility.AdvancedBase {
is_dir: 1,
uuid: uid,
parent_uid: await parent.get('uid'),
- path: path.join(await parent.get('path'), name),
+ path: _path.join(await parent.get('path'), name),
user_id: actor.type.user.id,
name,
created: ts,
@@ -559,7 +582,7 @@ class PuterFSProvider extends putility.AdvancedBase {
context: Context.get(),
});
- return node;
+ return node
} finally {
await lock_handle.unlock();
}
@@ -568,7 +591,7 @@ class PuterFSProvider extends putility.AdvancedBase {
/**
* Write a new file to the filesystem. Throws an error if the destination
* already exists.
- *
+ *
* @param {Object} param
* @param {Context} param.context
* @param {FSNode} param.parent: The parent directory of the file.
@@ -576,22 +599,25 @@ class PuterFSProvider extends putility.AdvancedBase {
* @param {File} param.file: The file to write.
* @returns {Promise}
*/
- async write_new({ context, parent, name, file }) {
- const {
- tmp, fsentry_tmp, message, actor: inputActor, app_id,
- } = context.values;
- const actor = inputActor ?? Context.get('actor');
+ async write_new({context, parent, name, file}) {
+ const { _path, uuidv4, config } = this.modules;
- const sizeService = this.#services.get('sizeService');
- const resourceService = this.#services.get('resourceService');
- const svc_fsEntry = this.#services.get('fsEntryService');
- const svc_event = this.#services.get('event');
- const fs = this.#services.get('filesystem');
+ const {
+ tmp, fsentry_tmp, message, actor: actor_let, app_id,
+ } = context.values;
+ let actor = actor_let ?? Context.get('actor');
+
+ const svc = Context.get('services');
+ const sizeService = svc.get('sizeService');
+ const resourceService = svc.get('resourceService');
+ const svc_fsEntry = svc.get('fsEntryService');
+ const svc_event = svc.get('event');
+ const fs = svc.get('filesystem');
// TODO: fs:decouple-versions
// add version hook externally so LLCWrite doesn't
// need direct database access
- const db = this.#services.get('database').get(DB_WRITE, 'filesystem');
+ const db = svc.get('database').get(DB_WRITE, 'filesystem');
const uid = uuidv4();
@@ -599,47 +625,47 @@ class PuterFSProvider extends putility.AdvancedBase {
let bucket_region = config.s3_region ?? config.region;
let bucket = config.s3_bucket;
- const svc_acl = this.#services.get('acl');
+ const svc_acl = context.get('services').get('acl');
if ( ! await svc_acl.check(actor, parent, 'write') ) {
throw await svc_acl.get_safe_acl_error(actor, parent, 'write');
}
- const storage_resp = await this.#storage_upload({
+ const storage_resp = await this._storage_upload({
uuid: uid,
- bucket,
- bucket_region,
- file,
+ bucket, bucket_region, file,
tmp: {
...tmp,
- path: path.join(await parent.get('path'), name),
- },
+ path: _path.join(await parent.get('path'), name),
+ }
});
fsentry_tmp.thumbnail = await fsentry_tmp.thumbnail_promise;
delete fsentry_tmp.thumbnail_promise;
- const timestamp = Math.round(Date.now() / 1000);
+ const ts = Math.round(Date.now() / 1000);
const raw_fsentry = {
uuid: uid,
is_dir: 0,
user_id: actor.type.user.id,
- created: timestamp,
- accessed: timestamp,
- modified: timestamp,
+ created: ts,
+ accessed: ts,
+ modified: ts,
parent_uid: await parent.get('uid'),
name,
size: file.size,
- path: path.join(await parent.get('path'), name),
+ path: _path.join(await parent.get('path'), name),
...fsentry_tmp,
+
bucket_region,
bucket,
+
associated_app_id: app_id ?? null,
};
svc_event.emit('fs.pending.file', {
fsentry: FSNodeContext.sanitize_pending_entry_info(raw_fsentry),
context,
- });
+ })
resourceService.register({
uid,
@@ -649,16 +675,6 @@ class PuterFSProvider extends putility.AdvancedBase {
const filesize = file.size;
sizeService.change_usage(actor.type.user.id, filesize);
- // Meter ingress
- const ownerId = await parent.get('user_id');
- const ownerActor = new Actor({
- type: new UserActorType({
- user: await get_user({ id: ownerId }),
- }),
- });
-
- this.#meteringService.incrementUsage(ownerActor, 'filesystem:ingress:bytes', filesize);
-
const entryOp = await svc_fsEntry.insert(raw_fsentry);
(async () => {
@@ -668,18 +684,20 @@ class PuterFSProvider extends putility.AdvancedBase {
const new_item_node = await fs.node(new NodeUIDSelector(uid));
const new_item = await new_item_node.get('entry');
const store_version_id = storage_resp.VersionId;
- if ( store_version_id ){
+ if( store_version_id ){
// insert version into db
- db.write('INSERT INTO `fsentry_versions` (`user_id`, `fsentry_id`, `fsentry_uuid`, `version_id`, `message`, `ts_epoch`) VALUES (?, ?, ?, ?, ?, ?)',
- [
- actor.type.user.id,
- new_item.id,
- new_item.uuid,
- store_version_id,
- message ?? null,
- timestamp,
- ]);
- }
+ db.write(
+ "INSERT INTO `fsentry_versions` (`user_id`, `fsentry_id`, `fsentry_uuid`, `version_id`, `message`, `ts_epoch`) VALUES (?, ?, ?, ?, ?, ?)",
+ [
+ actor.type.user.id,
+ new_item.id,
+ new_item.uuid,
+ store_version_id,
+ message ?? null,
+ ts,
+ ]
+ );
+ }
})();
const node = await fs.node(new NodeUIDSelector(uid));
@@ -695,7 +713,7 @@ class PuterFSProvider extends putility.AdvancedBase {
/**
* Overwrite an existing file. Throws an error if the destination does not
* exist.
- *
+ *
* @param {Object} param
* @param {Context} param.context
* @param {FSNodeContext} param.node: The node to write to.
@@ -704,21 +722,22 @@ class PuterFSProvider extends putility.AdvancedBase {
*/
async write_overwrite({ context, node, file }) {
const {
- tmp, fsentry_tmp, message, actor: inputActor,
+ tmp, fsentry_tmp, message, actor: actor_let
} = context.values;
- const actor = inputActor ?? Context.get('actor');
+ let actor = actor_let ?? Context.get('actor');
- const sizeService = this.#services.get('sizeService');
- const resourceService = this.#services.get('resourceService');
- const svc_fsEntry = this.#services.get('fsEntryService');
- const svc_event = this.#services.get('event');
+ const svc = Context.get('services');
+ const sizeService = svc.get('sizeService');
+ const resourceService = svc.get('resourceService');
+ const svc_fsEntry = svc.get('fsEntryService');
+ const svc_event = svc.get('event');
// TODO: fs:decouple-versions
// add version hook externally so LLCWrite doesn't
// need direct database access
- const db = this.#services.get('database').get(DB_WRITE, 'filesystem');
+ const db = svc.get('database').get(DB_WRITE, 'filesystem');
- const svc_acl = this.#services.get('acl');
+ const svc_acl = context.get('services').get('acl');
if ( ! await svc_acl.check(actor, node, 'write') ) {
throw await svc_acl.get_safe_acl_error(actor, node, 'write');
}
@@ -728,15 +747,13 @@ class PuterFSProvider extends putility.AdvancedBase {
const bucket_region = node.entry.bucket_region;
const bucket = node.entry.bucket;
- const state_upload = await this.#storage_upload({
+ const state_upload = await this._storage_upload({
uuid: node.entry.uuid,
- bucket,
- bucket_region,
- file,
+ bucket, bucket_region, file,
tmp: {
...tmp,
path: await node.get('path'),
- },
+ }
});
if ( fsentry_tmp?.thumbnail_promise ) {
@@ -760,15 +777,6 @@ class PuterFSProvider extends putility.AdvancedBase {
const filesize = file.size;
sizeService.change_usage(actor.type.user.id, filesize);
- // Meter ingress
- const ownerId = await node.get('user_id');
- const ownerActor = new Actor({
- type: new UserActorType({
- user: await get_user({ id: ownerId }),
- }),
- });
- this.#meteringService.incrementUsage(ownerActor, 'filesystem:ingress:bytes', filesize);
-
const entryOp = await svc_fsEntry.update(uid, raw_fsentry_delta);
// depends on fsentry, does not depend on S3
@@ -778,7 +786,7 @@ class PuterFSProvider extends putility.AdvancedBase {
})();
const cachePromise = (async () => {
- const svc_fileCache = this.#services.get('file-cache');
+ const svc_fileCache = context.get('services').get('file-cache');
await svc_fileCache.invalidate(node);
})();
@@ -791,7 +799,7 @@ class PuterFSProvider extends putility.AdvancedBase {
})();
// TODO (xiaochen): determine if this can be removed, post_insert handler need
- // to skip events from other servers (why? 1. current write logic is inside
+ // to skip events from other servers (why? 1. current write logic is inside
// the local server 2. broadcast system conduct "fire-and-forget" behavior)
state_upload.post_insert({
db, user: actor.type.user, node, uid, message, ts,
@@ -801,26 +809,24 @@ class PuterFSProvider extends putility.AdvancedBase {
return node;
}
- /**
- * @param {Object} param
- * @param {File} param.file: The file to write.
- * @returns
- */
- async #storage_upload({
+
+
+ async _storage_upload ({
uuid,
- bucket,
- bucket_region,
- file,
+ bucket, bucket_region, file,
tmp,
}) {
- const log = this.#services.get('log-service').create('fs.#storage_upload');
- const errors = this.#services.get('error-service').create(log);
- const svc_event = this.#services.get('event');
+ const { config } = this.modules;
- const svc_mountpoint = this.#services.get('mountpoint');
+ const svc = Context.get('services');
+ const log = svc.get('log-service').create('fs._storage_upload');
+ const errors = svc.get('error-service').create(log);
+ const svc_event = svc.get('event');
+
+ const svc_mountpoint = svc.get('mountpoint');
const storage = svc_mountpoint.get_storage(this.constructor.name);
- bucket ??= config.s3_bucket;
+ bucket ??= config.s3_bucket;
bucket_region ??= config.s3_region ?? config.region;
let upload_tracker = new UploadProgressTracker();
@@ -831,10 +837,10 @@ class PuterFSProvider extends putility.AdvancedBase {
meta: {
item_uid: uuid,
item_path: tmp.path,
- },
- });
+ }
+ })
- if ( !file.buffer ) {
+ if ( ! file.buffer ) {
let stream = file.stream;
let alarm_timeout = null;
stream = stuck_detector_stream(stream, {
@@ -861,9 +867,9 @@ class PuterFSProvider extends putility.AdvancedBase {
on_unstuck: () => {
clearTimeout(alarm_timeout);
this.frame.status = OperationFrame.FRAME_STATUS_WORKING;
- },
+ }
});
- file = { ...file, stream };
+ file = { ...file, stream, };
}
let hashPromise;
@@ -878,7 +884,7 @@ class PuterFSProvider extends putility.AdvancedBase {
}
hashPromise.then(hash => {
- const svc_event = this.#services.get('event');
+ const svc_event = Context.get('services').get('event');
svc_event.emit('outer.fs.write-hash', {
hash, uuid,
});
diff --git a/src/backend/src/routers/file.js b/src/backend/src/routers/file.js
index 20e7cf3c..3676433e 100644
--- a/src/backend/src/routers/file.js
+++ b/src/backend/src/routers/file.js
@@ -16,84 +16,78 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-'use strict';
+"use strict"
const express = require('express');
const router = new express.Router();
-const { subdomain, validate_signature_auth, get_url_from_req, get_descendants, id2path, get_user, sign_file } = require('../helpers');
+const {validate_signature_auth, get_url_from_req, get_descendants, id2path, get_user, sign_file} = require('../helpers');
const { DB_WRITE } = require('../services/database/consts');
const { Context } = require('../util/context');
-const { UserActorType } = require('../services/auth/Actor');
-const { Actor } = require('../services/auth/Actor');
// -----------------------------------------------------------------------//
// GET /file
// -----------------------------------------------------------------------//
-router.get('/file', async (req, res, next) => {
- // services and "services"
- /** @type {import('../services/MeteringService/MeteringService').MeteringAndBillingService} */
- const meteringService = req.services.get('meteringService').meteringAndBillingService;
- const log = req.services.get('log-service').create('/file');
- const errors = req.services.get('error-service').create(log);
- const db = req.services.get('database').get(DB_WRITE, 'filesystem');
-
+router.get('/file', async (req, res, next)=>{
// check subdomain
- if ( subdomain(req) !== 'api' ){
+ if(require('../helpers').subdomain(req) !== 'api')
next();
- }
// validate URL signature
- try {
+ try{
validate_signature_auth(get_url_from_req(req), 'read');
- } catch (e){
- console.log(e);
+ }
+ catch(e){
+ console.log(e)
return res.status(403).send(e);
}
-
+
let can_write = false;
- try {
+ try{
validate_signature_auth(get_url_from_req(req), 'write');
can_write = true;
- } catch ( _e ){
- // slent fail
- }
+ }catch(e){}
+
+ const log = req.services.get('log-service').create('/file');
+ const errors = req.services.get('error-service').create(log);
+
// modules
+ const db = req.services.get('database').get(DB_WRITE, 'filesystem');
+ const mime = require('mime-types')
+
const uid = req.query.uid;
let download = req.query.download ?? false;
- if ( download === 'true' || download === '1' || download === true ){
+ if(download === 'true' || download === '1' || download === true)
download = true;
- }
// retrieve FSEntry from db
- const fsentry = await db.read('SELECT * FROM fsentries WHERE uuid = ? LIMIT 1', [uid]);
+ const fsentry = await db.read(
+ `SELECT * FROM fsentries WHERE uuid = ? LIMIT 1`, [uid]
+ );
// FSEntry not found
- if ( !fsentry[0] )
- {
- return res.status(400).send({ message: 'No entry found with this uid' });
- }
+ if(!fsentry[0])
+ return res.status(400).send({message: 'No entry found with this uid'})
// check if item owner is suspended
- const user = await get_user({ id: fsentry[0].user_id });
- if ( user.suspended )
- {
- return res.status(401).send({ error: 'Account suspended' });
- }
+ const user = await get_user({id: fsentry[0].user_id});
+ if(user.suspended)
+ return res.status(401).send({error: 'Account suspended'});
// ---------------------------------------------------------------//
// FSEntry is dir
// ---------------------------------------------------------------//
- if ( fsentry[0].is_dir ){
+ if(fsentry[0].is_dir){
// convert to path
const dirpath = await id2path(fsentry[0].id);
+ console.log(dirpath, fsentry[0].user_id)
// get all children of this dir
- const children = await get_descendants(dirpath, await get_user({ id: fsentry[0].user_id }), 1);
+ const children = await get_descendants(dirpath, await get_user({id: fsentry[0].user_id}), 1);
const signed_children = [];
- if ( children.length > 0 ){
- for ( const child of children ){
+ if(children.length>0){
+ for(const child of children){
// sign file
const signed_child = await sign_file(child,
- can_write ? 'write' : 'read');
+ can_write ? 'write' : 'read');
signed_children.push(signed_child);
}
}
@@ -102,116 +96,108 @@ router.get('/file', async (req, res, next) => {
}
// force download?
- if ( download ){
+ if(download)
res.attachment(fsentry[0].name);
- }
// record fsentry owner
res.resource_owner = fsentry[0].user_id;
// try to deduce content-type
- const contentType = 'application/octet-stream';
+ const contentType = "application/octet-stream";
// update `accessed`
- db.write('UPDATE fsentries SET accessed = ? WHERE `id` = ?',
- [Date.now() / 1000, fsentry[0].id]);
+ db.write(
+ "UPDATE fsentries SET accessed = ? WHERE `id` = ?",
+ [Date.now()/1000, fsentry[0].id]
+ );
const range = req.headers.range;
- const ownerActor = new Actor({
- type: new UserActorType({
- user: user,
- }),
- });
- const fileSize = fsentry[0].size;
-
//--------------------------------------------------
// No range
//--------------------------------------------------
- if ( !range ){
+ if (!range) {
// set content-type, if available
- if ( contentType !== null ){
+ if(contentType !== null)
res.setHeader('Content-Type', contentType);
- }
const storage = req.ctx.get('storage');
// stream data from S3
- try {
+ try{
let stream = await storage.create_read_stream(fsentry[0].uuid, {
bucket: fsentry[0].bucket,
bucket_region: fsentry[0].bucket_region,
});
-
- meteringService.incrementUsage(ownerActor, 'filesystem:egress:bytes', fileSize);
return stream.pipe(res);
- } catch (e){
+ }catch(e){
errors.report('read from storage', {
source: e,
trace: true,
alarm: true,
});
- return res.type('application/json').status(500).send({ message: 'There was an internal problem reading the file.' });
+ return res.type('application/json').status(500).send({message: 'There was an internal problem reading the file.'});
}
}
//--------------------------------------------------
// Range
//--------------------------------------------------
- else {
+ else{
+ // get file size
+ const file_size = fsentry[0].size;
const total = fsentry[0].size;
const user_agent = req.get('User-Agent');
- let start, end, chunkSize = 5000000;
+ let start, end, CHUNK_SIZE = 5000000;
let is_safari = false;
// Parse range header
- var parts = range.replace(/bytes=/, '').split('-');
+ var parts = range.replace(/bytes=/, "").split("-");
var partialstart = parts[0];
var partialend = parts[1];
start = parseInt(partialstart, 10);
- end = partialend ? parseInt(partialend, 10) : total - 1;
+ end = partialend ? parseInt(partialend, 10) : total-1;
- if ( user_agent && user_agent.toLowerCase().includes('safari') && !user_agent.includes('Chrome') ){
- // Safari
+ // Safari
+ if(user_agent && user_agent.toLowerCase().includes('safari') && !user_agent.includes('Chrome')){
is_safari = true;
- chunkSize = (end - start) + 1;
- } else {
- // All other user agents
- end = Math.min(start + chunkSize, fileSize - 1);
+ CHUNK_SIZE = (end-start)+1;
+ }
+ // All other user agents
+ else{
+ end = Math.min(start + CHUNK_SIZE, file_size - 1);
}
// Create headers
const headers = {
- 'Content-Range': `bytes ${start}-${end}/${fileSize}`,
- 'Accept-Ranges': 'bytes',
- 'Content-Length': is_safari ? chunkSize : (end - start + 1),
+ "Content-Range": `bytes ${start}-${end}/${file_size}`,
+ "Accept-Ranges": "bytes",
+ "Content-Length": is_safari ? CHUNK_SIZE : (end-start+1),
};
// Set Content-Type, if available
- if ( contentType ){
- headers['Content-Type'] = contentType;
- }
+ if(contentType)
+ headers["Content-Type"] = contentType;
// HTTP Status 206 for Partial Content
res.writeHead(206, headers);
- try {
+ try{
const storage = Context.get('storage');
let stream = await storage.create_read_stream(fsentry[0].uuid, {
bucket: fsentry[0].bucket,
bucket_region: fsentry[0].bucket_region,
});
- meteringService.incrementUsage(ownerActor, 'filesystem:egress:bytes', chunkSize);
return stream.pipe(res);
- } catch (e){
+ }catch(e){
errors.report('read from storage', {
source: e,
trace: true,
alarm: true,
});
- return res.type('application/json').status(500).send({ message: 'There was an internal problem reading the file.' });
+ return res.type('application/json').status(500).send({message: 'There was an internal problem reading the file.'});
}
}
-});
+})
-module.exports = router;
+module.exports = router
diff --git a/src/backend/src/routers/login.js b/src/backend/src/routers/login.js
index aa00a9f2..bfc303f8 100644
--- a/src/backend/src/routers/login.js
+++ b/src/backend/src/routers/login.js
@@ -53,7 +53,7 @@ const complete_ = async ({ req, res, user }) => {
};
// -----------------------------------------------------------------------//
-// POST /login
+// POST /file
// -----------------------------------------------------------------------//
router.post('/login', express.json(), body_parser_error_handler,
// Add diagnostic middleware to log captcha data
diff --git a/src/backend/src/services/MeteringService/MeteringService.ts b/src/backend/src/services/MeteringService/MeteringService.ts
index 54ce19d7..194a1200 100644
--- a/src/backend/src/services/MeteringService/MeteringService.ts
+++ b/src/backend/src/services/MeteringService/MeteringService.ts
@@ -54,7 +54,7 @@ export class MeteringAndBillingService {
if ( totalCost === 0 && costOverride === undefined ) {
// could be something is off, there are some models that cost nothing from openrouter, but then our overrides should not be undefined, so will flag
- this.#alarmService.create('metering-service-warning', 'potential abuse vector', {
+ this.#alarmService.create('metering-service-warning', "potential abuse vector", {
actor,
usageType,
usageAmount,
@@ -125,7 +125,7 @@ export class MeteringAndBillingService {
const actorSubscriptionPromise = this.getActorSubscription(actor);
const actorAddonsPromise = this.getActorAddons(actor);
const [actorUsages, actorSubscription, actorAddons] = (await Promise.all([actorUsagesPromise, actorSubscriptionPromise, actorAddonsPromise]));
- if ( actorUsages.total > actorSubscription.monthUsageAllowance && actorAddons.purchasedCredits && actorAddons.purchasedCredits > (actorAddons.consumedPurchaseCredits || 0) ) {
+ if ( actorUsages.total > actorSubscription.monthUsageAllowance && actorAddons.purchasedCredits ) {
// if we are now over the allowance, start consuming purchased credits
const withinBoundsUsage = Math.max(0, actorSubscription.monthUsageAllowance - actorUsages.total + totalCost);
const overageUsage = totalCost - withinBoundsUsage;
@@ -139,17 +139,6 @@ export class MeteringAndBillingService {
});
}
}
- // alert if significantly over allowance and no purchased credits left
- if ( actorUsages.total > (actorSubscription.monthUsageAllowance) * 2 && (actorAddons.purchasedCredits || 0) <= (actorAddons.consumedPurchaseCredits || 0) ) {
- this.#alarmService.create('metering-service-usage-limit-exceeded', `Actor ${actorId} has exceeded their usage allowance significantly`, {
- actor,
- usageType,
- usageAmount,
- costOverride,
- totalUsage: actorUsages.total,
- monthUsageAllowance: actorSubscription.monthUsageAllowance,
- });
- }
return actorUsages;
});
} catch (e) {
diff --git a/src/backend/src/services/MeteringService/costMaps/awsPollyCostMap.ts b/src/backend/src/services/MeteringService/costMaps/awsPollyCostMap.ts
index bf98a556..708e0834 100644
--- a/src/backend/src/services/MeteringService/costMaps/awsPollyCostMap.ts
+++ b/src/backend/src/services/MeteringService/costMaps/awsPollyCostMap.ts
@@ -11,14 +11,14 @@
export const AWS_POLLY_COST_MAP = {
// Standard engine: $4.00 per 1M characters (400 microcents per character)
- 'aws-polly:standard:character': 400,
+ "aws-polly:standard:character": 400,
// Neural engine: $16.00 per 1M characters (1600 microcents per character)
- 'aws-polly:neural:character': 1600,
+ "aws-polly:neural:character": 1600,
// Long-form engine: $100.00 per 1M characters (10000 microcents per character)
- 'aws-polly:long-form:character': 10000,
+ "aws-polly:long-form:character": 10000,
// Generative engine: $30.00 per 1M characters (3000 microcents per character)
- 'aws-polly:generative:character': 3000,
+ "aws-polly:generative:character": 3000,
};
\ No newline at end of file
diff --git a/src/backend/src/services/MeteringService/costMaps/awsTextractCostMap.ts b/src/backend/src/services/MeteringService/costMaps/awsTextractCostMap.ts
index 4a0f078c..32572860 100644
--- a/src/backend/src/services/MeteringService/costMaps/awsTextractCostMap.ts
+++ b/src/backend/src/services/MeteringService/costMaps/awsTextractCostMap.ts
@@ -1,15 +1,15 @@
// AWS Textract Cost Map (page-based pricing for OCR)
-//
+//
// This map defines per-page pricing (in microcents) for AWS Textract OCR API.
// Pricing is based on the Detect Document Text API: $1.50 per 1,000 pages.
// Each entry is the cost per page for the specified API.
-//
+//
// Pattern: "aws-textract:{api}:page"
// Example: "aws-textract:detect-document-text:page" → 150 microcents per page
-//
+//
// Note: 1,000,000 microcents = $0.01 USD. $1.50 per 1,000 pages = $0.0015 per page = 0.15 cents per page = 150000 microcents per page.
//
export const AWS_TEXTRACT_COST_MAP = {
// Detect Document Text API: $1.50 per 1,000 pages (150000 microcents per page)
- 'aws-textract:detect-document-text:page': 150000,
+ "aws-textract:detect-document-text:page": 150000,
};
\ No newline at end of file
diff --git a/src/backend/src/services/MeteringService/costMaps/claudeCostMap.ts b/src/backend/src/services/MeteringService/costMaps/claudeCostMap.ts
index 0b9554f5..2016aa32 100644
--- a/src/backend/src/services/MeteringService/costMaps/claudeCostMap.ts
+++ b/src/backend/src/services/MeteringService/costMaps/claudeCostMap.ts
@@ -19,58 +19,64 @@
export const CLAUDE_COST_MAP = {
// Claude Sonnet 4.5
- 'claude:claude-sonnet-4-5-20250929:input_tokens': 300,
- 'claude:claude-sonnet-4-5-20250929:ephemeral_5m_input_tokens': 300 * 1.25,
- 'claude:claude-sonnet-4-5-20250929:ephemeral_1h_input_tokens': 300 * 2,
- 'claude:claude-sonnet-4-5-20250929:cache_read_input_tokens': 300 * 0.1,
- 'claude:claude-sonnet-4-5-20250929:output_tokens': 1500,
+ "claude:claude-sonnet-4-5-20250929:input_tokens": 300,
+ "claude:claude-sonnet-4-5-20250929:ephemeral_5m_input_tokens": 300 * 1.25,
+ "claude:claude-sonnet-4-5-20250929:ephemeral_1h_input_tokens": 300 * 2,
+ "claude:claude-sonnet-4-5-20250929:cache_read_input_tokens": 300 * 0.1,
+ "claude:claude-sonnet-4-5-20250929:output_tokens": 1500,
// Claude Opus 4.1
- 'claude:claude-opus-4-1-20250805:input_tokens': 1500,
- 'claude:claude-opus-4-1-20250805:ephemeral_5m_input_tokens': 1500 * 1.25,
- 'claude:claude-opus-4-1-20250805:ephemeral_1h_input_tokens': 1500 * 2,
- 'claude:claude-opus-4-1-20250805:cache_read_input_tokens': 1500 * 0.1,
- 'claude:claude-opus-4-1-20250805:output_tokens': 7500,
+ "claude:claude-opus-4-1-20250805:input_tokens": 1500,
+ "claude:claude-opus-4-1-20250805:ephemeral_5m_input_tokens": 1500 * 1.25,
+ "claude:claude-opus-4-1-20250805:ephemeral_1h_input_tokens": 1500 * 2,
+ "claude:claude-opus-4-1-20250805:cache_read_input_tokens": 1500 * 0.1,
+ "claude:claude-opus-4-1-20250805:output_tokens": 7500,
+
// Claude Opus 4
- 'claude:claude-opus-4-20250514:input_tokens': 1500,
- 'claude:claude-opus-4-20250514:ephemeral_5m_input_tokens': 1500 * 1.25,
- 'claude:claude-opus-4-20250514:ephemeral_1h_input_tokens': 1500 * 2,
- 'claude:claude-opus-4-20250514:cache_read_input_tokens': 1500 * 0.1,
- 'claude:claude-opus-4-20250514:output_tokens': 7500,
+ "claude:claude-opus-4-20250514:input_tokens": 1500,
+ "claude:claude-opus-4-20250514:ephemeral_5m_input_tokens": 1500 * 1.25,
+ "claude:claude-opus-4-20250514:ephemeral_1h_input_tokens": 1500 * 2,
+ "claude:claude-opus-4-20250514:cache_read_input_tokens": 1500 * 0.1,
+ "claude:claude-opus-4-20250514:output_tokens": 7500,
+
// Claude Sonnet 4
- 'claude:claude-sonnet-4-20250514:input_tokens': 300,
- 'claude:claude-sonnet-4-20250514:ephemeral_5m_input_tokens': 300 * 1.25,
- 'claude:claude-sonnet-4-20250514:ephemeral_1h_input_tokens': 300 * 2,
- 'claude:claude-sonnet-4-20250514:cache_read_input_tokens': 300 * 0.1,
- 'claude:claude-sonnet-4-20250514:output_tokens': 1500,
+ "claude:claude-sonnet-4-20250514:input_tokens": 300,
+ "claude:claude-sonnet-4-20250514:ephemeral_5m_input_tokens": 300 * 1.25,
+ "claude:claude-sonnet-4-20250514:ephemeral_1h_input_tokens": 300 * 2,
+ "claude:claude-sonnet-4-20250514:cache_read_input_tokens": 300 * 0.1,
+ "claude:claude-sonnet-4-20250514:output_tokens": 1500,
+
// Claude 3.7 Sonnet
- 'claude:claude-3-7-sonnet-20250219:input_tokens': 300,
- 'claude:claude-3-7-sonnet-20250219:ephemeral_5m_input_tokens': 300 * 1.25,
- 'claude:claude-3-7-sonnet-20250219:ephemeral_1h_input_tokens': 300 * 2,
- 'claude:claude-3-7-sonnet-20250219:cache_read_input_tokens': 300 * 0.1,
- 'claude:claude-3-7-sonnet-20250219:output_tokens': 1500,
+ "claude:claude-3-7-sonnet-20250219:input_tokens": 300,
+ "claude:claude-3-7-sonnet-20250219:ephemeral_5m_input_tokens": 300 * 1.25,
+ "claude:claude-3-7-sonnet-20250219:ephemeral_1h_input_tokens": 300 * 2,
+ "claude:claude-3-7-sonnet-20250219:cache_read_input_tokens": 300 * 0.1,
+ "claude:claude-3-7-sonnet-20250219:output_tokens": 1500,
+
// Claude 3.5 Sonnet (Oct 2024)
- 'claude:claude-3-5-sonnet-20241022:input_tokens': 300,
- 'claude:claude-3-5-sonnet-20241022:ephemeral_5m_input_tokens': 300 * 1.25,
- 'claude:claude-3-5-sonnet-20241022:ephemeral_1h_input_tokens': 300 * 2,
- 'claude:claude-3-5-sonnet-20241022:cache_read_input_tokens': 300 * 0.1,
- 'claude:claude-3-5-sonnet-20241022:output_tokens': 1500,
+ "claude:claude-3-5-sonnet-20241022:input_tokens": 300,
+ "claude:claude-3-5-sonnet-20241022:ephemeral_5m_input_tokens": 300 * 1.25,
+ "claude:claude-3-5-sonnet-20241022:ephemeral_1h_input_tokens": 300 * 2,
+ "claude:claude-3-5-sonnet-20241022:cache_read_input_tokens": 300 * 0.1,
+ "claude:claude-3-5-sonnet-20241022:output_tokens": 1500,
+
// Claude 3.5 Sonnet (June 2024)
- 'claude:claude-3-5-sonnet-20240620:input_tokens': 300,
- 'claude:claude-3-5-sonnet-20240620:ephemeral_5m_input_tokens': 300 * 1.25,
- 'claude:claude-3-5-sonnet-20240620:ephemeral_1h_input_tokens': 300 * 2,
- 'claude:claude-3-5-sonnet-20240620:cache_read_input_tokens': 300 * 0.1,
- 'claude:claude-3-5-sonnet-20240620:output_tokens': 1500,
+ "claude:claude-3-5-sonnet-20240620:input_tokens": 300,
+ "claude:claude-3-5-sonnet-20240620:ephemeral_5m_input_tokens": 300 * 1.25,
+ "claude:claude-3-5-sonnet-20240620:ephemeral_1h_input_tokens": 300 * 2,
+ "claude:claude-3-5-sonnet-20240620:cache_read_input_tokens": 300 * 0.1,
+ "claude:claude-3-5-sonnet-20240620:output_tokens": 1500,
+
// Claude 3 Haiku
- 'claude:claude-3-haiku-20240307:input_tokens': 25,
- 'claude:claude-3-haiku-20240307:ephemeral_5m_input_tokens': 25 * 1.25,
- 'claude:claude-3-haiku-20240307:ephemeral_1h_input_tokens': 25 * 2,
- 'claude:claude-3-haiku-20240307:cache_read_input_tokens': 25 * 0.1,
- 'claude:claude-3-haiku-20240307:output_tokens': 125,
+ "claude:claude-3-haiku-20240307:input_tokens": 25,
+ "claude:claude-3-haiku-20240307:ephemeral_5m_input_tokens": 25 * 1.25,
+ "claude:claude-3-haiku-20240307:ephemeral_1h_input_tokens": 25 * 2,
+ "claude:claude-3-haiku-20240307:cache_read_input_tokens": 25 * 0.1,
+ "claude:claude-3-haiku-20240307:output_tokens": 125
};
\ No newline at end of file
diff --git a/src/backend/src/services/MeteringService/costMaps/deepSeekCostMap.ts b/src/backend/src/services/MeteringService/costMaps/deepSeekCostMap.ts
index 2ea78659..0a58d53a 100644
--- a/src/backend/src/services/MeteringService/costMaps/deepSeekCostMap.ts
+++ b/src/backend/src/services/MeteringService/costMaps/deepSeekCostMap.ts
@@ -19,10 +19,10 @@
export const DEEPSEEK_COST_MAP = {
// DeepSeek Chat
- 'deepseek:deepseek-chat:prompt_tokens': 56,
- 'deepseek:deepseek-chat:completion_tokens': 168,
+ "deepseek:deepseek-chat:prompt_tokens": 56,
+ "deepseek:deepseek-chat:completion_tokens": 168,
// DeepSeek Reasoner
- 'deepseek:deepseek-reasoner:prompt_tokens': 56,
- 'deepseek:deepseek-reasoner:completion_tokens': 168,
+ "deepseek:deepseek-reasoner:prompt_tokens": 56,
+ "deepseek:deepseek-reasoner:completion_tokens": 168,
};
\ No newline at end of file
diff --git a/src/backend/src/services/MeteringService/costMaps/fileSystemCostMap.ts b/src/backend/src/services/MeteringService/costMaps/fileSystemCostMap.ts
deleted file mode 100644
index 6d6f3297..00000000
--- a/src/backend/src/services/MeteringService/costMaps/fileSystemCostMap.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { toMicroCents } from '../utils';
-
-export const FILE_SYSTEM_COST_MAP = {
- 'filesystem:ingress:bytes': 0,
- 'filesystem:delete:bytes': 0,
- 'filesystem:egress:bytes': toMicroCents(0.12 / 1024 / 1024 / 1024), // $0.11 per GB ~> 0.12 per GiB
- 'filesystem:cached-egress:bytes': toMicroCents(0.1 / 1024 / 1024 / 1024), // $0.09 per GB ~> 0.1 per GiB,
-};
\ No newline at end of file
diff --git a/src/backend/src/services/MeteringService/costMaps/geminiCostMap.ts b/src/backend/src/services/MeteringService/costMaps/geminiCostMap.ts
index 781fad19..a1220cfc 100644
--- a/src/backend/src/services/MeteringService/costMaps/geminiCostMap.ts
+++ b/src/backend/src/services/MeteringService/costMaps/geminiCostMap.ts
@@ -1,6 +1,6 @@
// TODO DS: these should be loaded from config or db eventually
-/**
+/**
* flat cost map based on usage types, numbers are in microcents (1/1 millionth of a cent)
* E.g. 1000000 microcents = 1 cent
* most services measure their prices in 1 million requests or tokens or whatever, so if that's the case you can simply use the cent val
@@ -9,9 +9,9 @@
*/
export const GEMINI_COST_MAP = {
// Gemini api usage types (costs per token in microcents)
- 'gemini:gemini-2.0-flash:promptTokenCount': 10,
- 'gemini:gemini-2.0-flash:candidatesTokenCount': 40,
- 'gemini:gemini-1.5-flash:promptTokenCount': 3,
- 'gemini:gemini-1.5-flash:candidatesTokenCount': 2,
- 'gemini:gemini-2.5-flash-image-preview:1024x1024': 3_900_000,
-};
+ "gemini:gemini-2.0-flash:promptTokenCount": 10,
+ "gemini:gemini-2.0-flash:candidatesTokenCount": 40,
+ "gemini:gemini-1.5-flash:promptTokenCount": 3,
+ "gemini:gemini-1.5-flash:candidatesTokenCount": 2,
+ "gemini:gemini-2.5-flash-image-preview:1024x1024": 3_900_000
+}
diff --git a/src/backend/src/services/MeteringService/costMaps/groqCostMap.ts b/src/backend/src/services/MeteringService/costMaps/groqCostMap.ts
index 3e680eb2..b5282ec3 100644
--- a/src/backend/src/services/MeteringService/costMaps/groqCostMap.ts
+++ b/src/backend/src/services/MeteringService/costMaps/groqCostMap.ts
@@ -19,52 +19,52 @@
export const GROQ_COST_MAP = {
// Gemma models
- 'groq:gemma2-9b-it:prompt_tokens': 20,
- 'groq:gemma2-9b-it:completion_tokens': 20,
- 'groq:gemma-7b-it:prompt_tokens': 7,
- 'groq:gemma-7b-it:completion_tokens': 7,
+ "groq:gemma2-9b-it:prompt_tokens": 20,
+ "groq:gemma2-9b-it:completion_tokens": 20,
+ "groq:gemma-7b-it:prompt_tokens": 7,
+ "groq:gemma-7b-it:completion_tokens": 7,
// Llama 3 Groq Tool Use Preview
- 'groq:llama3-groq-70b-8192-tool-use-preview:prompt_tokens': 89,
- 'groq:llama3-groq-70b-8192-tool-use-preview:completion_tokens': 89,
- 'groq:llama3-groq-8b-8192-tool-use-preview:prompt_tokens': 19,
- 'groq:llama3-groq-8b-8192-tool-use-preview:completion_tokens': 19,
+ "groq:llama3-groq-70b-8192-tool-use-preview:prompt_tokens": 89,
+ "groq:llama3-groq-70b-8192-tool-use-preview:completion_tokens": 89,
+ "groq:llama3-groq-8b-8192-tool-use-preview:prompt_tokens": 19,
+ "groq:llama3-groq-8b-8192-tool-use-preview:completion_tokens": 19,
// Llama 3.1
- 'groq:llama-3.1-70b-versatile:prompt_tokens': 59,
- 'groq:llama-3.1-70b-versatile:completion_tokens': 79,
- 'groq:llama-3.1-70b-specdec:prompt_tokens': 59,
- 'groq:llama-3.1-70b-specdec:completion_tokens': 99,
- 'groq:llama-3.1-8b-instant:prompt_tokens': 5,
- 'groq:llama-3.1-8b-instant:completion_tokens': 8,
+ "groq:llama-3.1-70b-versatile:prompt_tokens": 59,
+ "groq:llama-3.1-70b-versatile:completion_tokens": 79,
+ "groq:llama-3.1-70b-specdec:prompt_tokens": 59,
+ "groq:llama-3.1-70b-specdec:completion_tokens": 99,
+ "groq:llama-3.1-8b-instant:prompt_tokens": 5,
+ "groq:llama-3.1-8b-instant:completion_tokens": 8,
// Llama Guard
- 'groq:meta-llama/llama-guard-4-12b:prompt_tokens': 20,
- 'groq:meta-llama/llama-guard-4-12b:completion_tokens': 20,
- 'groq:llama-guard-3-8b:prompt_tokens': 20,
- 'groq:llama-guard-3-8b:completion_tokens': 20,
+ "groq:meta-llama/llama-guard-4-12b:prompt_tokens": 20,
+ "groq:meta-llama/llama-guard-4-12b:completion_tokens": 20,
+ "groq:llama-guard-3-8b:prompt_tokens": 20,
+ "groq:llama-guard-3-8b:completion_tokens": 20,
// Prompt Guard
- 'groq:meta-llama/llama-prompt-guard-2-86m:prompt_tokens': 4,
- 'groq:meta-llama/llama-prompt-guard-2-86m:completion_tokens': 4,
+ "groq:meta-llama/llama-prompt-guard-2-86m:prompt_tokens": 4,
+ "groq:meta-llama/llama-prompt-guard-2-86m:completion_tokens": 4,
// Llama 3.2 Preview
- 'groq:llama-3.2-1b-preview:prompt_tokens': 4,
- 'groq:llama-3.2-1b-preview:completion_tokens': 4,
- 'groq:llama-3.2-3b-preview:prompt_tokens': 6,
- 'groq:llama-3.2-3b-preview:completion_tokens': 6,
- 'groq:llama-3.2-11b-vision-preview:prompt_tokens': 18,
- 'groq:llama-3.2-11b-vision-preview:completion_tokens': 18,
- 'groq:llama-3.2-90b-vision-preview:prompt_tokens': 90,
- 'groq:llama-3.2-90b-vision-preview:completion_tokens': 90,
+ "groq:llama-3.2-1b-preview:prompt_tokens": 4,
+ "groq:llama-3.2-1b-preview:completion_tokens": 4,
+ "groq:llama-3.2-3b-preview:prompt_tokens": 6,
+ "groq:llama-3.2-3b-preview:completion_tokens": 6,
+ "groq:llama-3.2-11b-vision-preview:prompt_tokens": 18,
+ "groq:llama-3.2-11b-vision-preview:completion_tokens": 18,
+ "groq:llama-3.2-90b-vision-preview:prompt_tokens": 90,
+ "groq:llama-3.2-90b-vision-preview:completion_tokens": 90,
// Llama 3 8k/70B
- 'groq:llama3-70b-8192:prompt_tokens': 59,
- 'groq:llama3-70b-8192:completion_tokens': 79,
- 'groq:llama3-8b-8192:prompt_tokens': 5,
- 'groq:llama3-8b-8192:completion_tokens': 8,
+ "groq:llama3-70b-8192:prompt_tokens": 59,
+ "groq:llama3-70b-8192:completion_tokens": 79,
+ "groq:llama3-8b-8192:prompt_tokens": 5,
+ "groq:llama3-8b-8192:completion_tokens": 8,
// Mixtral
- 'groq:mixtral-8x7b-32768:prompt_tokens': 24,
- 'groq:mixtral-8x7b-32768:completion_tokens': 24,
+ "groq:mixtral-8x7b-32768:prompt_tokens": 24,
+ "groq:mixtral-8x7b-32768:completion_tokens": 24,
};
\ No newline at end of file
diff --git a/src/backend/src/services/MeteringService/costMaps/index.ts b/src/backend/src/services/MeteringService/costMaps/index.ts
index c7ec1653..7e93b393 100644
--- a/src/backend/src/services/MeteringService/costMaps/index.ts
+++ b/src/backend/src/services/MeteringService/costMaps/index.ts
@@ -1,17 +1,16 @@
-import { AWS_POLLY_COST_MAP } from './awsPollyCostMap';
-import { AWS_TEXTRACT_COST_MAP } from './awsTextractCostMap';
-import { CLAUDE_COST_MAP } from './claudeCostMap';
-import { DEEPSEEK_COST_MAP } from './deepSeekCostMap';
-import { FILE_SYSTEM_COST_MAP } from './fileSystemCostMap';
-import { GEMINI_COST_MAP } from './geminiCostMap';
-import { GROQ_COST_MAP } from './groqCostMap';
-import { KV_COST_MAP } from './kvCostMap';
-import { MISTRAL_COST_MAP } from './mistralCostMap';
-import { OPENAI_COST_MAP } from './openAiCostMap';
-import { OPENAI_IMAGE_COST_MAP } from './openaiImageCostMap';
-import { OPENROUTER_COST_MAP } from './openrouterCostMap';
-import { TOGETHER_COST_MAP } from './togetherCostMap';
-import { XAI_COST_MAP } from './xaiCostMap';
+import { AWS_POLLY_COST_MAP } from "./awsPollyCostMap";
+import { AWS_TEXTRACT_COST_MAP } from "./awsTextractCostMap";
+import { CLAUDE_COST_MAP } from "./claudeCostMap";
+import { DEEPSEEK_COST_MAP } from "./deepSeekCostMap";
+import { GEMINI_COST_MAP } from "./geminiCostMap";
+import { GROQ_COST_MAP } from "./groqCostMap";
+import { KV_COST_MAP } from "./kvCostMap";
+import { MISTRAL_COST_MAP } from "./mistralCostMap";
+import { OPENAI_COST_MAP } from "./openAiCostMap";
+import { OPENAI_IMAGE_COST_MAP } from "./openaiImageCostMap";
+import { OPENROUTER_COST_MAP } from "./openrouterCostMap";
+import { TOGETHER_COST_MAP } from "./togetherCostMap";
+import { XAI_COST_MAP } from "./xaiCostMap";
export const COST_MAPS = {
...AWS_POLLY_COST_MAP,
@@ -27,5 +26,4 @@ export const COST_MAPS = {
...OPENROUTER_COST_MAP,
...TOGETHER_COST_MAP,
...XAI_COST_MAP,
- ...FILE_SYSTEM_COST_MAP,
};
\ No newline at end of file
diff --git a/src/backend/src/services/MeteringService/costMaps/kvCostMap.ts b/src/backend/src/services/MeteringService/costMaps/kvCostMap.ts
index ba79456b..d9f95b33 100644
--- a/src/backend/src/services/MeteringService/costMaps/kvCostMap.ts
+++ b/src/backend/src/services/MeteringService/costMaps/kvCostMap.ts
@@ -2,4 +2,4 @@ export const KV_COST_MAP = {
// Map with unit to cost measurements in microcent
'kv:read': 63,
'kv:write': 125,
-};
+}
diff --git a/src/backend/src/services/MeteringService/costMaps/mistralCostMap.ts b/src/backend/src/services/MeteringService/costMaps/mistralCostMap.ts
index 70a84b24..528f6db7 100644
--- a/src/backend/src/services/MeteringService/costMaps/mistralCostMap.ts
+++ b/src/backend/src/services/MeteringService/costMaps/mistralCostMap.ts
@@ -19,42 +19,42 @@
export const MISTRAL_COST_MAP = {
// Mistral models (values in microcents/token, from MistralAIService.js)
- 'mistral:mistral-large-latest:prompt_tokens': 200,
- 'mistral:mistral-large-latest:completion_tokens': 600,
- 'mistral:pixtral-large-latest:prompt_tokens': 200,
- 'mistral:pixtral-large-latest:completion_tokens': 600,
- 'mistral:mistral-small-latest:prompt_tokens': 20,
- 'mistral:mistral-small-latest:completion_tokens': 60,
- 'mistral:codestral-latest:prompt_tokens': 30,
- 'mistral:codestral-latest:completion_tokens': 90,
- 'mistral:ministral-8b-latest:prompt_tokens': 10,
- 'mistral:ministral-8b-latest:completion_tokens': 10,
- 'mistral:ministral-3b-latest:prompt_tokens': 4,
- 'mistral:ministral-3b-latest:completion_tokens': 4,
- 'mistral:pixtral-12b:prompt_tokens': 15,
- 'mistral:pixtral-12b:completion_tokens': 15,
- 'mistral:mistral-nemo:prompt_tokens': 15,
- 'mistral:mistral-nemo:completion_tokens': 15,
- 'mistral:open-mistral-7b:prompt_tokens': 25,
- 'mistral:open-mistral-7b:completion_tokens': 25,
- 'mistral:open-mixtral-8x7b:prompt_tokens': 7,
- 'mistral:open-mixtral-8x7b:completion_tokens': 7,
- 'mistral:open-mixtral-8x22b:prompt_tokens': 2,
- 'mistral:open-mixtral-8x22b:completion_tokens': 6,
- 'mistral:magistral-medium-latest:prompt_tokens': 200,
- 'mistral:magistral-medium-latest:completion_tokens': 500,
- 'mistral:magistral-small-latest:prompt_tokens': 10,
- 'mistral:magistral-small-latest:completion_tokens': 10,
- 'mistral:mistral-medium-latest:prompt_tokens': 40,
- 'mistral:mistral-medium-latest:completion_tokens': 200,
- 'mistral:mistral-moderation-latest:prompt_tokens': 10,
- 'mistral:mistral-moderation-latest:completion_tokens': 10,
- 'mistral:devstral-small-latest:prompt_tokens': 10,
- 'mistral:devstral-small-latest:completion_tokens': 10,
- 'mistral:mistral-saba-latest:prompt_tokens': 20,
- 'mistral:mistral-saba-latest:completion_tokens': 60,
- 'mistral:open-mistral-nemo:prompt_tokens': 10,
- 'mistral:open-mistral-nemo:completion_tokens': 10,
- 'mistral:mistral-ocr-latest:prompt_tokens': 100,
- 'mistral:mistral-ocr-latest:completion_tokens': 300,
+ "mistral:mistral-large-latest:prompt_tokens": 200,
+ "mistral:mistral-large-latest:completion_tokens": 600,
+ "mistral:pixtral-large-latest:prompt_tokens": 200,
+ "mistral:pixtral-large-latest:completion_tokens": 600,
+ "mistral:mistral-small-latest:prompt_tokens": 20,
+ "mistral:mistral-small-latest:completion_tokens": 60,
+ "mistral:codestral-latest:prompt_tokens": 30,
+ "mistral:codestral-latest:completion_tokens": 90,
+ "mistral:ministral-8b-latest:prompt_tokens": 10,
+ "mistral:ministral-8b-latest:completion_tokens": 10,
+ "mistral:ministral-3b-latest:prompt_tokens": 4,
+ "mistral:ministral-3b-latest:completion_tokens": 4,
+ "mistral:pixtral-12b:prompt_tokens": 15,
+ "mistral:pixtral-12b:completion_tokens": 15,
+ "mistral:mistral-nemo:prompt_tokens": 15,
+ "mistral:mistral-nemo:completion_tokens": 15,
+ "mistral:open-mistral-7b:prompt_tokens": 25,
+ "mistral:open-mistral-7b:completion_tokens": 25,
+ "mistral:open-mixtral-8x7b:prompt_tokens": 7,
+ "mistral:open-mixtral-8x7b:completion_tokens": 7,
+ "mistral:open-mixtral-8x22b:prompt_tokens": 2,
+ "mistral:open-mixtral-8x22b:completion_tokens": 6,
+ "mistral:magistral-medium-latest:prompt_tokens": 200,
+ "mistral:magistral-medium-latest:completion_tokens": 500,
+ "mistral:magistral-small-latest:prompt_tokens": 10,
+ "mistral:magistral-small-latest:completion_tokens": 10,
+ "mistral:mistral-medium-latest:prompt_tokens": 40,
+ "mistral:mistral-medium-latest:completion_tokens": 200,
+ "mistral:mistral-moderation-latest:prompt_tokens": 10,
+ "mistral:mistral-moderation-latest:completion_tokens": 10,
+ "mistral:devstral-small-latest:prompt_tokens": 10,
+ "mistral:devstral-small-latest:completion_tokens": 10,
+ "mistral:mistral-saba-latest:prompt_tokens": 20,
+ "mistral:mistral-saba-latest:completion_tokens": 60,
+ "mistral:open-mistral-nemo:prompt_tokens": 10,
+ "mistral:open-mistral-nemo:completion_tokens": 10,
+ "mistral:mistral-ocr-latest:prompt_tokens": 100,
+ "mistral:mistral-ocr-latest:completion_tokens": 300,
};
\ No newline at end of file
diff --git a/src/backend/src/services/MeteringService/costMaps/openAiCostMap.ts b/src/backend/src/services/MeteringService/costMaps/openAiCostMap.ts
index a2db853f..c50eee97 100644
--- a/src/backend/src/services/MeteringService/costMaps/openAiCostMap.ts
+++ b/src/backend/src/services/MeteringService/costMaps/openAiCostMap.ts
@@ -20,58 +20,58 @@
export const OPENAI_COST_MAP = {
// GPT-5 models
- 'openai:gpt-5-2025-08-07:prompt_tokens': 125,
- 'openai:gpt-5-2025-08-07:cached_tokens': 13,
- 'openai:gpt-5-2025-08-07:completion_tokens': 1000,
- 'openai:gpt-5-mini-2025-08-07:prompt_tokens': 25,
- 'openai:gpt-5-mini-2025-08-07:cached_tokens': 3,
- 'openai:gpt-5-mini-2025-08-07:completion_tokens': 200,
- 'openai:gpt-5-nano-2025-08-07:prompt_tokens': 5,
- 'openai:gpt-5-nano-2025-08-07:cached_tokens': 1,
- 'openai:gpt-5-nano-2025-08-07:completion_tokens': 40,
- 'openai:gpt-5-chat-latest:prompt_tokens': 125,
- 'openai:gpt-5-chat-latest:cached_tokens': 13,
- 'openai:gpt-5-chat-latest:completion_tokens': 1000,
+ "openai:gpt-5-2025-08-07:prompt_tokens": 125,
+ "openai:gpt-5-2025-08-07:cached_tokens": 13,
+ "openai:gpt-5-2025-08-07:completion_tokens": 1000,
+ "openai:gpt-5-mini-2025-08-07:prompt_tokens": 25,
+ "openai:gpt-5-mini-2025-08-07:cached_tokens": 3,
+ "openai:gpt-5-mini-2025-08-07:completion_tokens": 200,
+ "openai:gpt-5-nano-2025-08-07:prompt_tokens": 5,
+ "openai:gpt-5-nano-2025-08-07:cached_tokens": 1,
+ "openai:gpt-5-nano-2025-08-07:completion_tokens": 40,
+ "openai:gpt-5-chat-latest:prompt_tokens": 125,
+ "openai:gpt-5-chat-latest:cached_tokens": 13,
+ "openai:gpt-5-chat-latest:completion_tokens": 1000,
// GPT-4o models
- 'openai:gpt-4o:prompt_tokens': 250,
- 'openai:gpt-4o:cached_tokens': 125,
- 'openai:gpt-4o:completion_tokens': 1000,
- 'openai:gpt-4o-mini:prompt_tokens': 15,
- 'openai:gpt-4o-mini:cached_tokens': 8,
- 'openai:gpt-4o-mini:completion_tokens': 60,
+ "openai:gpt-4o:prompt_tokens": 250,
+ "openai:gpt-4o:cached_tokens": 125,
+ "openai:gpt-4o:completion_tokens": 1000,
+ "openai:gpt-4o-mini:prompt_tokens": 15,
+ "openai:gpt-4o-mini:cached_tokens": 8,
+ "openai:gpt-4o-mini:completion_tokens": 60,
// O1 models
- 'openai:o1:prompt_tokens': 1500,
- 'openai:o1:cached_tokens': 750,
- 'openai:o1:completion_tokens': 6000,
- 'openai:o1-mini:prompt_tokens': 110,
- 'openai:o1-mini:completion_tokens': 440,
- 'openai:o1-pro:prompt_tokens': 15000,
- 'openai:o1-pro:completion_tokens': 60000,
+ "openai:o1:prompt_tokens": 1500,
+ "openai:o1:cached_tokens": 750,
+ "openai:o1:completion_tokens": 6000,
+ "openai:o1-mini:prompt_tokens": 110,
+ "openai:o1-mini:completion_tokens": 440,
+ "openai:o1-pro:prompt_tokens": 15000,
+ "openai:o1-pro:completion_tokens": 60000,
// O3 models
- 'openai:o3:prompt_tokens': 1000,
- 'openai:o3:completion_tokens': 4000,
- 'openai:o3-mini:prompt_tokens': 110,
- 'openai:o3-mini:completion_tokens': 440,
+ "openai:o3:prompt_tokens": 1000,
+ "openai:o3:completion_tokens": 4000,
+ "openai:o3-mini:prompt_tokens": 110,
+ "openai:o3-mini:completion_tokens": 440,
// O4 models
- 'openai:o4-mini:prompt_tokens': 110,
- 'openai:o4-mini:completion_tokens': 440,
+ "openai:o4-mini:prompt_tokens": 110,
+ "openai:o4-mini:completion_tokens": 440,
// GPT-4.1 models
- 'openai:gpt-4.1:prompt_tokens': 200,
- 'openai:gpt-4.1:cached_tokens': 50,
- 'openai:gpt-4.1:completion_tokens': 800,
- 'openai:gpt-4.1-mini:prompt_tokens': 40,
- 'openai:gpt-4.1-mini:cached_tokens': 10,
- 'openai:gpt-4.1-mini:completion_tokens': 160,
- 'openai:gpt-4.1-nano:prompt_tokens': 10,
- 'openai:gpt-4.1-nano:cached_tokens': 2,
- 'openai:gpt-4.1-nano:completion_tokens': 40,
+ "openai:gpt-4.1:prompt_tokens": 200,
+ "openai:gpt-4.1:cached_tokens": 50,
+ "openai:gpt-4.1:completion_tokens": 800,
+ "openai:gpt-4.1-mini:prompt_tokens": 40,
+ "openai:gpt-4.1-mini:cached_tokens": 10,
+ "openai:gpt-4.1-mini:completion_tokens": 160,
+ "openai:gpt-4.1-nano:prompt_tokens": 10,
+ "openai:gpt-4.1-nano:cached_tokens": 2,
+ "openai:gpt-4.1-nano:completion_tokens": 40,
// GPT-4.5 preview
- 'openai:gpt-4.5-preview:prompt_tokens': 7500,
- 'openai:gpt-4.5-preview:completion_tokens': 15000,
+ "openai:gpt-4.5-preview:prompt_tokens": 7500,
+ "openai:gpt-4.5-preview:completion_tokens": 15000,
};
diff --git a/src/backend/src/services/MeteringService/costMaps/openaiImageCostMap.ts b/src/backend/src/services/MeteringService/costMaps/openaiImageCostMap.ts
index 7b645621..ba34af45 100644
--- a/src/backend/src/services/MeteringService/costMaps/openaiImageCostMap.ts
+++ b/src/backend/src/services/MeteringService/costMaps/openaiImageCostMap.ts
@@ -3,30 +3,30 @@
// All costs are in microcents (1/1,000,000th of a cent). Example: 1,000,000 microcents = $0.01 USD.//
// Naming pattern: "openai:{model}:{size}" or "openai:{model}:hd:{size}" for HD images
-import { toMicroCents } from '../utils';
+import { toMicroCents } from "../utils";
export const OPENAI_IMAGE_COST_MAP = {
// DALL-E 3
- 'openai:dall-e-3:1024x1024': toMicroCents(0.04), // $0.04
- 'openai:dall-e-3:1024x1792': toMicroCents(0.08), // $0.08
- 'openai:dall-e-3:1792x1024': toMicroCents(0.08), // $0.08
- 'openai:dall-e-3:hd:1024x1024': toMicroCents(0.08), // $0.08
- 'openai:dall-e-3:hd:1024x1792': toMicroCents(0.12), // $0.12
- 'openai:dall-e-3:hd:1792x1024': toMicroCents(0.12), // $0.12
+ "openai:dall-e-3:1024x1024": toMicroCents(0.04), // $0.04
+ "openai:dall-e-3:1024x1792": toMicroCents(0.08), // $0.08
+ "openai:dall-e-3:1792x1024": toMicroCents(0.08), // $0.08
+ "openai:dall-e-3:hd:1024x1024": toMicroCents(0.08), // $0.08
+ "openai:dall-e-3:hd:1024x1792": toMicroCents(0.12), // $0.12
+ "openai:dall-e-3:hd:1792x1024": toMicroCents(0.12), // $0.12
// DALL-E 2
- 'openai:dall-e-2:1024x1024': toMicroCents(0.02), // $0.02
- 'openai:dall-e-2:512x512': toMicroCents(0.018), // $0.018
- 'openai:dall-e-2:256x256': toMicroCents(0.016), // $0.016
+ "openai:dall-e-2:1024x1024": toMicroCents(0.02), // $0.02
+ "openai:dall-e-2:512x512": toMicroCents(0.018), // $0.018
+ "openai:dall-e-2:256x256": toMicroCents(0.016), // $0.016
// gpt-image-1
- 'openai:gpt-image-1:low:1024x1024': toMicroCents(0.011),
- 'openai:gpt-image-1:low:1024x1536': toMicroCents(0.016),
- 'openai:gpt-image-1:low:1536x1024': toMicroCents(0.016),
- 'openai:gpt-image-1:medium:1024x1024': toMicroCents(0.042),
- 'openai:gpt-image-1:medium:1024x1536': toMicroCents(0.063),
- 'openai:gpt-image-1:medium:1536x1024': toMicroCents(0.063),
- 'openai:gpt-image-1:high:1024x1024': toMicroCents(0.167),
- 'openai:gpt-image-1:high:1024x1536': toMicroCents(0.25),
- 'openai:gpt-image-1:high:1536x1024': toMicroCents(0.25),
+ "openai:gpt-image-1:low:1024x1024": toMicroCents(0.011),
+ "openai:gpt-image-1:low:1024x1536": toMicroCents(0.016),
+ "openai:gpt-image-1:low:1536x1024": toMicroCents(0.016),
+ "openai:gpt-image-1:medium:1024x1024": toMicroCents(0.042),
+ "openai:gpt-image-1:medium:1024x1536": toMicroCents(0.063),
+ "openai:gpt-image-1:medium:1536x1024": toMicroCents(0.063),
+ "openai:gpt-image-1:high:1024x1024": toMicroCents(0.167),
+ "openai:gpt-image-1:high:1024x1536": toMicroCents(0.25),
+ "openai:gpt-image-1:high:1536x1024": toMicroCents(0.25),
};
\ No newline at end of file
diff --git a/src/backend/src/services/MeteringService/costMaps/openrouterCostMap.ts b/src/backend/src/services/MeteringService/costMaps/openrouterCostMap.ts
index 469e3d68..81b6f4b6 100644
--- a/src/backend/src/services/MeteringService/costMaps/openrouterCostMap.ts
+++ b/src/backend/src/services/MeteringService/costMaps/openrouterCostMap.ts
@@ -1,722 +1,722 @@
export const OPENROUTER_COST_MAP = {
- 'openrouter:anthropic/claude-haiku-4.5:prompt': 100,
- 'openrouter:anthropic/claude-haiku-4.5:completion': 500,
- 'openrouter:anthropic/claude-haiku-4.5:input_cache_read': 10,
- 'openrouter:anthropic/claude-haiku-4.5:input_cache_write': 125,
- 'openrouter:qwen/qwen3-vl-8b-thinking:prompt': 18,
- 'openrouter:qwen/qwen3-vl-8b-thinking:completion': 210,
- 'openrouter:qwen/qwen3-vl-8b-instruct:prompt': 18,
- 'openrouter:qwen/qwen3-vl-8b-instruct:completion': 69,
- 'openrouter:inclusionai/ling-1t:prompt': 100,
- 'openrouter:inclusionai/ling-1t:completion': 300,
- 'openrouter:openai/o3-deep-research:prompt': 1000,
- 'openrouter:openai/o3-deep-research:completion': 4000,
- 'openrouter:openai/o3-deep-research:image': 765000,
- 'openrouter:openai/o3-deep-research:web_search': 1000000,
- 'openrouter:openai/o3-deep-research:input_cache_read': 250,
- 'openrouter:openai/o4-mini-deep-research:prompt': 200,
- 'openrouter:openai/o4-mini-deep-research:completion': 800,
- 'openrouter:openai/o4-mini-deep-research:image': 153000,
- 'openrouter:openai/o4-mini-deep-research:web_search': 1000000,
- 'openrouter:openai/o4-mini-deep-research:input_cache_read': 50,
- 'openrouter:nvidia/llama-3.3-nemotron-super-49b-v1.5:prompt': 10,
- 'openrouter:nvidia/llama-3.3-nemotron-super-49b-v1.5:completion': 40,
- 'openrouter:baidu/ernie-4.5-21b-a3b-thinking:prompt': 7,
- 'openrouter:baidu/ernie-4.5-21b-a3b-thinking:completion': 28,
- 'openrouter:google/gemini-2.5-flash-image:prompt': 30,
- 'openrouter:google/gemini-2.5-flash-image:completion': 250,
- 'openrouter:google/gemini-2.5-flash-image:image': 123800,
- 'openrouter:qwen/qwen3-vl-30b-a3b-thinking:prompt': 29,
- 'openrouter:qwen/qwen3-vl-30b-a3b-thinking:completion': 100,
- 'openrouter:qwen/qwen3-vl-30b-a3b-instruct:prompt': 29,
- 'openrouter:qwen/qwen3-vl-30b-a3b-instruct:completion': 99,
- 'openrouter:openai/gpt-5-pro:prompt': 1500,
- 'openrouter:openai/gpt-5-pro:completion': 12000,
- 'openrouter:z-ai/glm-4.6:prompt': 50,
- 'openrouter:z-ai/glm-4.6:completion': 175,
- 'openrouter:anthropic/claude-sonnet-4.5:prompt': 300,
- 'openrouter:anthropic/claude-sonnet-4.5:completion': 1500,
- 'openrouter:deepseek/deepseek-v3.2-exp:prompt': 27,
- 'openrouter:deepseek/deepseek-v3.2-exp:completion': 40,
- 'openrouter:thedrummer/cydonia-24b-v4.1:prompt': 30,
- 'openrouter:thedrummer/cydonia-24b-v4.1:completion': 50,
- 'openrouter:relace/relace-apply-3:prompt': 85,
- 'openrouter:relace/relace-apply-3:completion': 125,
- 'openrouter:google/gemini-2.5-flash-preview-09-2025:prompt': 30,
- 'openrouter:google/gemini-2.5-flash-preview-09-2025:completion': 250,
- 'openrouter:google/gemini-2.5-flash-preview-09-2025:image': 123800,
- 'openrouter:google/gemini-2.5-flash-preview-09-2025:input_cache_read': 7,
- 'openrouter:google/gemini-2.5-flash-preview-09-2025:input_cache_write': 38,
- 'openrouter:google/gemini-2.5-flash-lite-preview-09-2025:prompt': 10,
- 'openrouter:google/gemini-2.5-flash-lite-preview-09-2025:completion': 40,
- 'openrouter:qwen/qwen3-vl-235b-a22b-thinking:prompt': 45,
- 'openrouter:qwen/qwen3-vl-235b-a22b-thinking:completion': 350,
- 'openrouter:qwen/qwen3-vl-235b-a22b-instruct:prompt': 30,
- 'openrouter:qwen/qwen3-vl-235b-a22b-instruct:completion': 120,
- 'openrouter:qwen/qwen3-max:prompt': 120,
- 'openrouter:qwen/qwen3-max:completion': 600,
- 'openrouter:qwen/qwen3-max:input_cache_read': 24,
- 'openrouter:qwen/qwen3-coder-plus:prompt': 100,
- 'openrouter:qwen/qwen3-coder-plus:completion': 500,
- 'openrouter:qwen/qwen3-coder-plus:input_cache_read': 10,
- 'openrouter:openai/gpt-5-codex:prompt': 125,
- 'openrouter:openai/gpt-5-codex:completion': 1000,
- 'openrouter:openai/gpt-5-codex:input_cache_read': 12,
- 'openrouter:deepseek/deepseek-v3.1-terminus:prompt': 23,
- 'openrouter:deepseek/deepseek-v3.1-terminus:completion': 90,
- 'openrouter:x-ai/grok-4-fast:prompt': 20,
- 'openrouter:x-ai/grok-4-fast:completion': 50,
- 'openrouter:x-ai/grok-4-fast:input_cache_read': 5,
- 'openrouter:alibaba/tongyi-deepresearch-30b-a3b:prompt': 9,
- 'openrouter:alibaba/tongyi-deepresearch-30b-a3b:completion': 40,
- 'openrouter:qwen/qwen3-coder-flash:prompt': 30,
- 'openrouter:qwen/qwen3-coder-flash:completion': 150,
- 'openrouter:qwen/qwen3-coder-flash:input_cache_read': 8,
- 'openrouter:arcee-ai/afm-4.5b:prompt': 5,
- 'openrouter:arcee-ai/afm-4.5b:completion': 15,
- 'openrouter:opengvlab/internvl3-78b:prompt': 7,
- 'openrouter:opengvlab/internvl3-78b:completion': 26,
- 'openrouter:qwen/qwen3-next-80b-a3b-thinking:prompt': 14,
- 'openrouter:qwen/qwen3-next-80b-a3b-thinking:completion': 120,
- 'openrouter:qwen/qwen3-next-80b-a3b-instruct:prompt': 10,
- 'openrouter:qwen/qwen3-next-80b-a3b-instruct:completion': 80,
- 'openrouter:meituan/longcat-flash-chat:prompt': 15,
- 'openrouter:meituan/longcat-flash-chat:completion': 75,
- 'openrouter:qwen/qwen-plus-2025-07-28:prompt': 40,
- 'openrouter:qwen/qwen-plus-2025-07-28:completion': 120,
- 'openrouter:qwen/qwen-plus-2025-07-28:thinking:prompt': 40,
- 'openrouter:qwen/qwen-plus-2025-07-28:thinking:completion': 400,
- 'openrouter:nvidia/nemotron-nano-9b-v2:prompt': 4,
- 'openrouter:nvidia/nemotron-nano-9b-v2:completion': 16,
- 'openrouter:moonshotai/kimi-k2-0905:prompt': 39,
- 'openrouter:moonshotai/kimi-k2-0905:completion': 190,
- 'openrouter:deepcogito/cogito-v2-preview-llama-109b-moe:prompt': 18,
- 'openrouter:deepcogito/cogito-v2-preview-llama-109b-moe:completion': 59,
- 'openrouter:deepcogito/cogito-v2-preview-deepseek-671b:prompt': 125,
- 'openrouter:deepcogito/cogito-v2-preview-deepseek-671b:completion': 125,
- 'openrouter:stepfun-ai/step3:prompt': 57,
- 'openrouter:stepfun-ai/step3:completion': 142,
- 'openrouter:qwen/qwen3-30b-a3b-thinking-2507:prompt': 8,
- 'openrouter:qwen/qwen3-30b-a3b-thinking-2507:completion': 29,
- 'openrouter:x-ai/grok-code-fast-1:prompt': 20,
- 'openrouter:x-ai/grok-code-fast-1:completion': 150,
- 'openrouter:x-ai/grok-code-fast-1:input_cache_read': 2,
- 'openrouter:nousresearch/hermes-4-70b:prompt': 11,
- 'openrouter:nousresearch/hermes-4-70b:completion': 38,
- 'openrouter:nousresearch/hermes-4-405b:prompt': 30,
- 'openrouter:nousresearch/hermes-4-405b:completion': 120,
- 'openrouter:google/gemini-2.5-flash-image-preview:prompt': 30,
- 'openrouter:google/gemini-2.5-flash-image-preview:completion': 250,
- 'openrouter:google/gemini-2.5-flash-image-preview:image': 123800,
- 'openrouter:deepseek/deepseek-chat-v3.1:prompt': 20,
- 'openrouter:deepseek/deepseek-chat-v3.1:completion': 80,
- 'openrouter:openai/gpt-4o-audio-preview:prompt': 250,
- 'openrouter:openai/gpt-4o-audio-preview:completion': 1000,
- 'openrouter:openai/gpt-4o-audio-preview:audio': 4000,
- 'openrouter:mistralai/mistral-medium-3.1:prompt': 40,
- 'openrouter:mistralai/mistral-medium-3.1:completion': 200,
- 'openrouter:baidu/ernie-4.5-21b-a3b:prompt': 7,
- 'openrouter:baidu/ernie-4.5-21b-a3b:completion': 28,
- 'openrouter:baidu/ernie-4.5-vl-28b-a3b:prompt': 14,
- 'openrouter:baidu/ernie-4.5-vl-28b-a3b:completion': 56,
- 'openrouter:z-ai/glm-4.5v:prompt': 60,
- 'openrouter:z-ai/glm-4.5v:completion': 180,
- 'openrouter:z-ai/glm-4.5v:input_cache_read': 11,
- 'openrouter:ai21/jamba-mini-1.7:prompt': 20,
- 'openrouter:ai21/jamba-mini-1.7:completion': 40,
- 'openrouter:ai21/jamba-large-1.7:prompt': 200,
- 'openrouter:ai21/jamba-large-1.7:completion': 800,
- 'openrouter:openai/gpt-5-chat:prompt': 125,
- 'openrouter:openai/gpt-5-chat:completion': 1000,
- 'openrouter:openai/gpt-5-chat:input_cache_read': 12,
- 'openrouter:openai/gpt-5:prompt': 125,
- 'openrouter:openai/gpt-5:completion': 1000,
- 'openrouter:openai/gpt-5:web_search': 1000000,
- 'openrouter:openai/gpt-5:input_cache_read': 12,
- 'openrouter:openai/gpt-5-mini:prompt': 25,
- 'openrouter:openai/gpt-5-mini:completion': 200,
- 'openrouter:openai/gpt-5-mini:web_search': 1000000,
- 'openrouter:openai/gpt-5-mini:input_cache_read': 3,
- 'openrouter:openai/gpt-5-nano:prompt': 5,
- 'openrouter:openai/gpt-5-nano:completion': 40,
- 'openrouter:openai/gpt-5-nano:web_search': 1000000,
- 'openrouter:openai/gpt-5-nano:input_cache_read': 1,
- 'openrouter:openai/gpt-oss-120b:prompt': 4,
- 'openrouter:openai/gpt-oss-120b:completion': 40,
- 'openrouter:openai/gpt-oss-20b:prompt': 3,
- 'openrouter:openai/gpt-oss-20b:completion': 14,
- 'openrouter:anthropic/claude-opus-4.1:prompt': 1500,
- 'openrouter:anthropic/claude-opus-4.1:completion': 7500,
- 'openrouter:anthropic/claude-opus-4.1:image': 2400000,
- 'openrouter:anthropic/claude-opus-4.1:input_cache_read': 150,
- 'openrouter:anthropic/claude-opus-4.1:input_cache_write': 1875,
- 'openrouter:mistralai/codestral-2508:prompt': 30,
- 'openrouter:mistralai/codestral-2508:completion': 90,
- 'openrouter:qwen/qwen3-coder-30b-a3b-instruct:prompt': 6,
- 'openrouter:qwen/qwen3-coder-30b-a3b-instruct:completion': 25,
- 'openrouter:qwen/qwen3-30b-a3b-instruct-2507:prompt': 8,
- 'openrouter:qwen/qwen3-30b-a3b-instruct-2507:completion': 33,
- 'openrouter:z-ai/glm-4.5:prompt': 35,
- 'openrouter:z-ai/glm-4.5:completion': 155,
- 'openrouter:z-ai/glm-4.5-air:prompt': 14,
- 'openrouter:z-ai/glm-4.5-air:completion': 86,
- 'openrouter:qwen/qwen3-235b-a22b-thinking-2507:prompt': 11,
- 'openrouter:qwen/qwen3-235b-a22b-thinking-2507:completion': 60,
- 'openrouter:z-ai/glm-4-32b:prompt': 10,
- 'openrouter:z-ai/glm-4-32b:completion': 10,
- 'openrouter:qwen/qwen3-coder:prompt': 22,
- 'openrouter:qwen/qwen3-coder:completion': 95,
- 'openrouter:bytedance/ui-tars-1.5-7b:prompt': 10,
- 'openrouter:bytedance/ui-tars-1.5-7b:completion': 20,
- 'openrouter:google/gemini-2.5-flash-lite:prompt': 10,
- 'openrouter:google/gemini-2.5-flash-lite:completion': 40,
- 'openrouter:google/gemini-2.5-flash-lite:input_cache_read': 1,
- 'openrouter:google/gemini-2.5-flash-lite:input_cache_write': 18,
- 'openrouter:qwen/qwen3-235b-a22b-2507:prompt': 8,
- 'openrouter:qwen/qwen3-235b-a22b-2507:completion': 55,
- 'openrouter:switchpoint/router:prompt': 85,
- 'openrouter:switchpoint/router:completion': 340,
- 'openrouter:moonshotai/kimi-k2:prompt': 14,
- 'openrouter:moonshotai/kimi-k2:completion': 249,
- 'openrouter:thudm/glm-4.1v-9b-thinking:prompt': 4,
- 'openrouter:thudm/glm-4.1v-9b-thinking:completion': 14,
- 'openrouter:mistralai/devstral-medium:prompt': 40,
- 'openrouter:mistralai/devstral-medium:completion': 200,
- 'openrouter:mistralai/devstral-small:prompt': 7,
- 'openrouter:mistralai/devstral-small:completion': 28,
- 'openrouter:x-ai/grok-4:prompt': 300,
- 'openrouter:x-ai/grok-4:completion': 1500,
- 'openrouter:x-ai/grok-4:input_cache_read': 75,
- 'openrouter:tencent/hunyuan-a13b-instruct:prompt': 3,
- 'openrouter:tencent/hunyuan-a13b-instruct:completion': 3,
- 'openrouter:tngtech/deepseek-r1t2-chimera:prompt': 30,
- 'openrouter:tngtech/deepseek-r1t2-chimera:completion': 120,
- 'openrouter:morph/morph-v3-large:prompt': 90,
- 'openrouter:morph/morph-v3-large:completion': 190,
- 'openrouter:morph/morph-v3-fast:prompt': 80,
- 'openrouter:morph/morph-v3-fast:completion': 120,
- 'openrouter:baidu/ernie-4.5-vl-424b-a47b:prompt': 42,
- 'openrouter:baidu/ernie-4.5-vl-424b-a47b:completion': 125,
- 'openrouter:baidu/ernie-4.5-300b-a47b:prompt': 28,
- 'openrouter:baidu/ernie-4.5-300b-a47b:completion': 110,
- 'openrouter:thedrummer/anubis-70b-v1.1:prompt': 65,
- 'openrouter:thedrummer/anubis-70b-v1.1:completion': 100,
- 'openrouter:inception/mercury:prompt': 25,
- 'openrouter:inception/mercury:completion': 100,
- 'openrouter:mistralai/mistral-small-3.2-24b-instruct:prompt': 6,
- 'openrouter:mistralai/mistral-small-3.2-24b-instruct:completion': 18,
- 'openrouter:minimax/minimax-m1:prompt': 40,
- 'openrouter:minimax/minimax-m1:completion': 220,
- 'openrouter:google/gemini-2.5-flash-lite-preview-06-17:prompt': 10,
- 'openrouter:google/gemini-2.5-flash-lite-preview-06-17:completion': 40,
- 'openrouter:google/gemini-2.5-flash-lite-preview-06-17:audio': 30,
- 'openrouter:google/gemini-2.5-flash-lite-preview-06-17:input_cache_read': 3,
- 'openrouter:google/gemini-2.5-flash-lite-preview-06-17:input_cache_write': 18,
- 'openrouter:google/gemini-2.5-flash:prompt': 30,
- 'openrouter:google/gemini-2.5-flash:completion': 250,
- 'openrouter:google/gemini-2.5-flash:image': 123800,
- 'openrouter:google/gemini-2.5-flash:input_cache_read': 3,
- 'openrouter:google/gemini-2.5-flash:input_cache_write': 38,
- 'openrouter:google/gemini-2.5-pro:prompt': 125,
- 'openrouter:google/gemini-2.5-pro:completion': 1000,
- 'openrouter:google/gemini-2.5-pro:image': 516000,
- 'openrouter:google/gemini-2.5-pro:input_cache_read': 12,
- 'openrouter:google/gemini-2.5-pro:input_cache_write': 163,
- 'openrouter:moonshotai/kimi-dev-72b:prompt': 29,
- 'openrouter:moonshotai/kimi-dev-72b:completion': 115,
- 'openrouter:openai/o3-pro:prompt': 2000,
- 'openrouter:openai/o3-pro:completion': 8000,
- 'openrouter:openai/o3-pro:image': 1530000,
- 'openrouter:openai/o3-pro:web_search': 1000000,
- 'openrouter:x-ai/grok-3-mini:prompt': 30,
- 'openrouter:x-ai/grok-3-mini:completion': 50,
- 'openrouter:x-ai/grok-3-mini:input_cache_read': 7,
- 'openrouter:x-ai/grok-3:prompt': 300,
- 'openrouter:x-ai/grok-3:completion': 1500,
- 'openrouter:x-ai/grok-3:input_cache_read': 75,
- 'openrouter:mistralai/magistral-small-2506:prompt': 50,
- 'openrouter:mistralai/magistral-small-2506:completion': 150,
- 'openrouter:mistralai/magistral-medium-2506:prompt': 200,
- 'openrouter:mistralai/magistral-medium-2506:completion': 500,
- 'openrouter:mistralai/magistral-medium-2506:thinking:prompt': 200,
- 'openrouter:mistralai/magistral-medium-2506:thinking:completion': 500,
- 'openrouter:google/gemini-2.5-pro-preview:prompt': 125,
- 'openrouter:google/gemini-2.5-pro-preview:completion': 1000,
- 'openrouter:google/gemini-2.5-pro-preview:image': 516000,
- 'openrouter:google/gemini-2.5-pro-preview:input_cache_read': 31,
- 'openrouter:google/gemini-2.5-pro-preview:input_cache_write': 163,
- 'openrouter:deepseek/deepseek-r1-0528-qwen3-8b:prompt': 3,
- 'openrouter:deepseek/deepseek-r1-0528-qwen3-8b:completion': 11,
- 'openrouter:deepseek/deepseek-r1-0528:prompt': 40,
- 'openrouter:deepseek/deepseek-r1-0528:completion': 175,
- 'openrouter:anthropic/claude-opus-4:prompt': 1500,
- 'openrouter:anthropic/claude-opus-4:completion': 7500,
- 'openrouter:anthropic/claude-opus-4:image': 2400000,
- 'openrouter:anthropic/claude-opus-4:input_cache_read': 150,
- 'openrouter:anthropic/claude-opus-4:input_cache_write': 1875,
- 'openrouter:anthropic/claude-sonnet-4:prompt': 300,
- 'openrouter:anthropic/claude-sonnet-4:completion': 1500,
- 'openrouter:anthropic/claude-sonnet-4:image': 480000,
- 'openrouter:anthropic/claude-sonnet-4:input_cache_read': 30,
- 'openrouter:anthropic/claude-sonnet-4:input_cache_write': 375,
- 'openrouter:mistralai/devstral-small-2505:prompt': 5,
- 'openrouter:mistralai/devstral-small-2505:completion': 22,
- 'openrouter:google/gemma-3n-e4b-it:prompt': 2,
- 'openrouter:google/gemma-3n-e4b-it:completion': 4,
- 'openrouter:openai/codex-mini:prompt': 150,
- 'openrouter:openai/codex-mini:completion': 600,
- 'openrouter:openai/codex-mini:input_cache_read': 38,
- 'openrouter:nousresearch/deephermes-3-mistral-24b-preview:prompt': 15,
- 'openrouter:nousresearch/deephermes-3-mistral-24b-preview:completion': 59,
- 'openrouter:mistralai/mistral-medium-3:prompt': 40,
- 'openrouter:mistralai/mistral-medium-3:completion': 200,
- 'openrouter:google/gemini-2.5-pro-preview-05-06:prompt': 125,
- 'openrouter:google/gemini-2.5-pro-preview-05-06:completion': 1000,
- 'openrouter:google/gemini-2.5-pro-preview-05-06:image': 516000,
- 'openrouter:google/gemini-2.5-pro-preview-05-06:input_cache_read': 31,
- 'openrouter:google/gemini-2.5-pro-preview-05-06:input_cache_write': 163,
- 'openrouter:arcee-ai/spotlight:prompt': 18,
- 'openrouter:arcee-ai/spotlight:completion': 18,
- 'openrouter:arcee-ai/maestro-reasoning:prompt': 90,
- 'openrouter:arcee-ai/maestro-reasoning:completion': 330,
- 'openrouter:arcee-ai/virtuoso-large:prompt': 75,
- 'openrouter:arcee-ai/virtuoso-large:completion': 120,
- 'openrouter:arcee-ai/coder-large:prompt': 50,
- 'openrouter:arcee-ai/coder-large:completion': 80,
- 'openrouter:microsoft/phi-4-reasoning-plus:prompt': 7,
- 'openrouter:microsoft/phi-4-reasoning-plus:completion': 35,
- 'openrouter:inception/mercury-coder:prompt': 25,
- 'openrouter:inception/mercury-coder:completion': 100,
- 'openrouter:deepseek/deepseek-prover-v2:prompt': 50,
- 'openrouter:deepseek/deepseek-prover-v2:completion': 218,
- 'openrouter:meta-llama/llama-guard-4-12b:prompt': 18,
- 'openrouter:meta-llama/llama-guard-4-12b:completion': 18,
- 'openrouter:qwen/qwen3-30b-a3b:prompt': 6,
- 'openrouter:qwen/qwen3-30b-a3b:completion': 22,
- 'openrouter:qwen/qwen3-8b:prompt': 4,
- 'openrouter:qwen/qwen3-8b:completion': 14,
- 'openrouter:qwen/qwen3-14b:prompt': 5,
- 'openrouter:qwen/qwen3-14b:completion': 22,
- 'openrouter:qwen/qwen3-32b:prompt': 5,
- 'openrouter:qwen/qwen3-32b:completion': 20,
- 'openrouter:qwen/qwen3-235b-a22b:prompt': 18,
- 'openrouter:qwen/qwen3-235b-a22b:completion': 54,
- 'openrouter:tngtech/deepseek-r1t-chimera:prompt': 30,
- 'openrouter:tngtech/deepseek-r1t-chimera:completion': 120,
- 'openrouter:microsoft/mai-ds-r1:prompt': 30,
- 'openrouter:microsoft/mai-ds-r1:completion': 120,
- 'openrouter:thudm/glm-z1-32b:prompt': 5,
- 'openrouter:thudm/glm-z1-32b:completion': 22,
- 'openrouter:openai/o4-mini-high:prompt': 110,
- 'openrouter:openai/o4-mini-high:completion': 440,
- 'openrouter:openai/o4-mini-high:image': 84150,
- 'openrouter:openai/o4-mini-high:web_search': 1000000,
- 'openrouter:openai/o4-mini-high:input_cache_read': 28,
- 'openrouter:openai/o3:prompt': 200,
- 'openrouter:openai/o3:completion': 800,
- 'openrouter:openai/o3:image': 153000,
- 'openrouter:openai/o3:web_search': 1000000,
- 'openrouter:openai/o3:input_cache_read': 50,
- 'openrouter:openai/o4-mini:prompt': 110,
- 'openrouter:openai/o4-mini:completion': 440,
- 'openrouter:openai/o4-mini:image': 84150,
- 'openrouter:openai/o4-mini:web_search': 1000000,
- 'openrouter:openai/o4-mini:input_cache_read': 28,
- 'openrouter:shisa-ai/shisa-v2-llama3.3-70b:prompt': 5,
- 'openrouter:shisa-ai/shisa-v2-llama3.3-70b:completion': 22,
- 'openrouter:qwen/qwen2.5-coder-7b-instruct:prompt': 3,
- 'openrouter:qwen/qwen2.5-coder-7b-instruct:completion': 9,
- 'openrouter:openai/gpt-4.1:prompt': 200,
- 'openrouter:openai/gpt-4.1:completion': 800,
- 'openrouter:openai/gpt-4.1:web_search': 1000000,
- 'openrouter:openai/gpt-4.1:input_cache_read': 50,
- 'openrouter:openai/gpt-4.1-mini:prompt': 40,
- 'openrouter:openai/gpt-4.1-mini:completion': 160,
- 'openrouter:openai/gpt-4.1-mini:web_search': 1000000,
- 'openrouter:openai/gpt-4.1-mini:input_cache_read': 10,
- 'openrouter:openai/gpt-4.1-nano:prompt': 10,
- 'openrouter:openai/gpt-4.1-nano:completion': 40,
- 'openrouter:openai/gpt-4.1-nano:web_search': 1000000,
- 'openrouter:openai/gpt-4.1-nano:input_cache_read': 3,
- 'openrouter:eleutherai/llemma_7b:prompt': 80,
- 'openrouter:eleutherai/llemma_7b:completion': 120,
- 'openrouter:alfredpros/codellama-7b-instruct-solidity:prompt': 80,
- 'openrouter:alfredpros/codellama-7b-instruct-solidity:completion': 120,
- 'openrouter:arliai/qwq-32b-arliai-rpr-v1:prompt': 3,
- 'openrouter:arliai/qwq-32b-arliai-rpr-v1:completion': 11,
- 'openrouter:agentica-org/deepcoder-14b-preview:prompt': 1,
- 'openrouter:agentica-org/deepcoder-14b-preview:completion': 1,
- 'openrouter:x-ai/grok-3-mini-beta:prompt': 30,
- 'openrouter:x-ai/grok-3-mini-beta:completion': 50,
- 'openrouter:x-ai/grok-3-mini-beta:input_cache_read': 7,
- 'openrouter:x-ai/grok-3-beta:prompt': 300,
- 'openrouter:x-ai/grok-3-beta:completion': 1500,
- 'openrouter:x-ai/grok-3-beta:input_cache_read': 75,
- 'openrouter:nvidia/llama-3.1-nemotron-ultra-253b-v1:prompt': 60,
- 'openrouter:nvidia/llama-3.1-nemotron-ultra-253b-v1:completion': 180,
- 'openrouter:meta-llama/llama-4-maverick:prompt': 15,
- 'openrouter:meta-llama/llama-4-maverick:completion': 60,
- 'openrouter:meta-llama/llama-4-maverick:image': 66840,
- 'openrouter:meta-llama/llama-4-scout:prompt': 8,
- 'openrouter:meta-llama/llama-4-scout:completion': 30,
- 'openrouter:meta-llama/llama-4-scout:image': 33420,
- 'openrouter:allenai/molmo-7b-d:prompt': 10,
- 'openrouter:allenai/molmo-7b-d:completion': 20,
- 'openrouter:qwen/qwen2.5-vl-32b-instruct:prompt': 5,
- 'openrouter:qwen/qwen2.5-vl-32b-instruct:completion': 22,
- 'openrouter:deepseek/deepseek-chat-v3-0324:prompt': 24,
- 'openrouter:deepseek/deepseek-chat-v3-0324:completion': 84,
- 'openrouter:openai/o1-pro:prompt': 15000,
- 'openrouter:openai/o1-pro:completion': 60000,
- 'openrouter:openai/o1-pro:image': 21675000,
- 'openrouter:mistralai/mistral-small-3.1-24b-instruct:prompt': 5,
- 'openrouter:mistralai/mistral-small-3.1-24b-instruct:completion': 10,
- 'openrouter:allenai/olmo-2-0325-32b-instruct:prompt': 20,
- 'openrouter:allenai/olmo-2-0325-32b-instruct:completion': 35,
- 'openrouter:google/gemma-3-4b-it:prompt': 2,
- 'openrouter:google/gemma-3-4b-it:completion': 7,
- 'openrouter:google/gemma-3-12b-it:prompt': 3,
- 'openrouter:google/gemma-3-12b-it:completion': 10,
- 'openrouter:cohere/command-a:prompt': 250,
- 'openrouter:cohere/command-a:completion': 1000,
- 'openrouter:openai/gpt-4o-mini-search-preview:prompt': 15,
- 'openrouter:openai/gpt-4o-mini-search-preview:completion': 60,
- 'openrouter:openai/gpt-4o-mini-search-preview:request': 2750000,
- 'openrouter:openai/gpt-4o-mini-search-preview:image': 21700,
- 'openrouter:openai/gpt-4o-search-preview:prompt': 250,
- 'openrouter:openai/gpt-4o-search-preview:completion': 1000,
- 'openrouter:openai/gpt-4o-search-preview:request': 3500000,
- 'openrouter:openai/gpt-4o-search-preview:image': 361300,
- 'openrouter:google/gemma-3-27b-it:prompt': 9,
- 'openrouter:google/gemma-3-27b-it:completion': 16,
- 'openrouter:google/gemma-3-27b-it:image': 2560,
- 'openrouter:thedrummer/skyfall-36b-v2:prompt': 8,
- 'openrouter:thedrummer/skyfall-36b-v2:completion': 33,
- 'openrouter:microsoft/phi-4-multimodal-instruct:prompt': 5,
- 'openrouter:microsoft/phi-4-multimodal-instruct:completion': 10,
- 'openrouter:microsoft/phi-4-multimodal-instruct:image': 17685,
- 'openrouter:perplexity/sonar-reasoning-pro:prompt': 200,
- 'openrouter:perplexity/sonar-reasoning-pro:completion': 800,
- 'openrouter:perplexity/sonar-reasoning-pro:web_search': 500000,
- 'openrouter:perplexity/sonar-pro:prompt': 300,
- 'openrouter:perplexity/sonar-pro:completion': 1500,
- 'openrouter:perplexity/sonar-pro:web_search': 500000,
- 'openrouter:perplexity/sonar-deep-research:prompt': 200,
- 'openrouter:perplexity/sonar-deep-research:completion': 800,
- 'openrouter:perplexity/sonar-deep-research:web_search': 500000,
- 'openrouter:perplexity/sonar-deep-research:internal_reasoning': 300,
- 'openrouter:qwen/qwq-32b:prompt': 15,
- 'openrouter:qwen/qwq-32b:completion': 40,
- 'openrouter:nousresearch/deephermes-3-llama-3-8b-preview:prompt': 3,
- 'openrouter:nousresearch/deephermes-3-llama-3-8b-preview:completion': 11,
- 'openrouter:google/gemini-2.0-flash-lite-001:prompt': 7,
- 'openrouter:google/gemini-2.0-flash-lite-001:completion': 30,
- 'openrouter:anthropic/claude-3.7-sonnet:prompt': 300,
- 'openrouter:anthropic/claude-3.7-sonnet:completion': 1500,
- 'openrouter:anthropic/claude-3.7-sonnet:image': 480000,
- 'openrouter:anthropic/claude-3.7-sonnet:input_cache_read': 30,
- 'openrouter:anthropic/claude-3.7-sonnet:input_cache_write': 375,
- 'openrouter:anthropic/claude-3.7-sonnet:thinking:prompt': 300,
- 'openrouter:anthropic/claude-3.7-sonnet:thinking:completion': 1500,
- 'openrouter:anthropic/claude-3.7-sonnet:thinking:image': 480000,
- 'openrouter:anthropic/claude-3.7-sonnet:thinking:input_cache_read': 30,
- 'openrouter:anthropic/claude-3.7-sonnet:thinking:input_cache_write': 375,
- 'openrouter:mistralai/mistral-saba:prompt': 20,
- 'openrouter:mistralai/mistral-saba:completion': 60,
- 'openrouter:cognitivecomputations/dolphin3.0-mistral-24b:prompt': 4,
- 'openrouter:cognitivecomputations/dolphin3.0-mistral-24b:completion': 17,
- 'openrouter:meta-llama/llama-guard-3-8b:prompt': 2,
- 'openrouter:meta-llama/llama-guard-3-8b:completion': 6,
- 'openrouter:openai/o3-mini-high:prompt': 110,
- 'openrouter:openai/o3-mini-high:completion': 440,
- 'openrouter:openai/o3-mini-high:input_cache_read': 55,
- 'openrouter:google/gemini-2.0-flash-001:prompt': 10,
- 'openrouter:google/gemini-2.0-flash-001:completion': 40,
- 'openrouter:google/gemini-2.0-flash-001:image': 2580,
- 'openrouter:google/gemini-2.0-flash-001:audio': 70,
- 'openrouter:google/gemini-2.0-flash-001:input_cache_read': 3,
- 'openrouter:google/gemini-2.0-flash-001:input_cache_write': 18,
- 'openrouter:qwen/qwen-vl-plus:prompt': 21,
- 'openrouter:qwen/qwen-vl-plus:completion': 63,
- 'openrouter:qwen/qwen-vl-plus:image': 26880,
- 'openrouter:aion-labs/aion-1.0:prompt': 400,
- 'openrouter:aion-labs/aion-1.0:completion': 800,
- 'openrouter:aion-labs/aion-1.0-mini:prompt': 70,
- 'openrouter:aion-labs/aion-1.0-mini:completion': 140,
- 'openrouter:aion-labs/aion-rp-llama-3.1-8b:prompt': 20,
- 'openrouter:aion-labs/aion-rp-llama-3.1-8b:completion': 20,
- 'openrouter:qwen/qwen-vl-max:prompt': 80,
- 'openrouter:qwen/qwen-vl-max:completion': 320,
- 'openrouter:qwen/qwen-vl-max:image': 102400,
- 'openrouter:qwen/qwen-turbo:prompt': 5,
- 'openrouter:qwen/qwen-turbo:completion': 20,
- 'openrouter:qwen/qwen-turbo:input_cache_read': 2,
- 'openrouter:qwen/qwen2.5-vl-72b-instruct:prompt': 8,
- 'openrouter:qwen/qwen2.5-vl-72b-instruct:completion': 33,
- 'openrouter:qwen/qwen-plus:prompt': 40,
- 'openrouter:qwen/qwen-plus:completion': 120,
- 'openrouter:qwen/qwen-plus:input_cache_read': 16,
- 'openrouter:qwen/qwen-max:prompt': 160,
- 'openrouter:qwen/qwen-max:completion': 640,
- 'openrouter:qwen/qwen-max:input_cache_read': 64,
- 'openrouter:openai/o3-mini:prompt': 110,
- 'openrouter:openai/o3-mini:completion': 440,
- 'openrouter:openai/o3-mini:input_cache_read': 55,
- 'openrouter:mistralai/mistral-small-24b-instruct-2501:prompt': 5,
- 'openrouter:mistralai/mistral-small-24b-instruct-2501:completion': 8,
- 'openrouter:deepseek/deepseek-r1-distill-qwen-32b:prompt': 27,
- 'openrouter:deepseek/deepseek-r1-distill-qwen-32b:completion': 27,
- 'openrouter:deepseek/deepseek-r1-distill-qwen-14b:prompt': 15,
- 'openrouter:deepseek/deepseek-r1-distill-qwen-14b:completion': 15,
- 'openrouter:perplexity/sonar-reasoning:prompt': 100,
- 'openrouter:perplexity/sonar-reasoning:completion': 500,
- 'openrouter:perplexity/sonar-reasoning:request': 500000,
- 'openrouter:perplexity/sonar:prompt': 100,
- 'openrouter:perplexity/sonar:completion': 100,
- 'openrouter:perplexity/sonar:request': 500000,
- 'openrouter:liquid/lfm-7b:prompt': 1,
- 'openrouter:liquid/lfm-7b:completion': 1,
- 'openrouter:liquid/lfm-3b:prompt': 2,
- 'openrouter:liquid/lfm-3b:completion': 2,
- 'openrouter:deepseek/deepseek-r1-distill-llama-70b:prompt': 3,
- 'openrouter:deepseek/deepseek-r1-distill-llama-70b:completion': 13,
- 'openrouter:deepseek/deepseek-r1:prompt': 40,
- 'openrouter:deepseek/deepseek-r1:completion': 200,
- 'openrouter:minimax/minimax-01:prompt': 20,
- 'openrouter:minimax/minimax-01:completion': 110,
- 'openrouter:mistralai/codestral-2501:prompt': 30,
- 'openrouter:mistralai/codestral-2501:completion': 90,
- 'openrouter:microsoft/phi-4:prompt': 6,
- 'openrouter:microsoft/phi-4:completion': 14,
- 'openrouter:sao10k/l3.1-70b-hanami-x1:prompt': 300,
- 'openrouter:sao10k/l3.1-70b-hanami-x1:completion': 300,
- 'openrouter:deepseek/deepseek-chat:prompt': 30,
- 'openrouter:deepseek/deepseek-chat:completion': 85,
- 'openrouter:sao10k/l3.3-euryale-70b:prompt': 65,
- 'openrouter:sao10k/l3.3-euryale-70b:completion': 75,
- 'openrouter:openai/o1:prompt': 1500,
- 'openrouter:openai/o1:completion': 6000,
- 'openrouter:openai/o1:image': 2167500,
- 'openrouter:openai/o1:input_cache_read': 750,
- 'openrouter:cohere/command-r7b-12-2024:prompt': 4,
- 'openrouter:cohere/command-r7b-12-2024:completion': 15,
- 'openrouter:meta-llama/llama-3.3-70b-instruct:prompt': 13,
- 'openrouter:meta-llama/llama-3.3-70b-instruct:completion': 39,
- 'openrouter:amazon/nova-lite-v1:prompt': 6,
- 'openrouter:amazon/nova-lite-v1:completion': 24,
- 'openrouter:amazon/nova-lite-v1:image': 9000,
- 'openrouter:amazon/nova-micro-v1:prompt': 4,
- 'openrouter:amazon/nova-micro-v1:completion': 14,
- 'openrouter:amazon/nova-pro-v1:prompt': 80,
- 'openrouter:amazon/nova-pro-v1:completion': 320,
- 'openrouter:amazon/nova-pro-v1:image': 120000,
- 'openrouter:openai/gpt-4o-2024-11-20:prompt': 250,
- 'openrouter:openai/gpt-4o-2024-11-20:completion': 1000,
- 'openrouter:openai/gpt-4o-2024-11-20:image': 361300,
- 'openrouter:openai/gpt-4o-2024-11-20:input_cache_read': 125,
- 'openrouter:mistralai/mistral-large-2411:prompt': 200,
- 'openrouter:mistralai/mistral-large-2411:completion': 600,
- 'openrouter:mistralai/mistral-large-2407:prompt': 200,
- 'openrouter:mistralai/mistral-large-2407:completion': 600,
- 'openrouter:mistralai/pixtral-large-2411:prompt': 200,
- 'openrouter:mistralai/pixtral-large-2411:completion': 600,
- 'openrouter:mistralai/pixtral-large-2411:image': 288800,
- 'openrouter:qwen/qwen-2.5-coder-32b-instruct:prompt': 4,
- 'openrouter:qwen/qwen-2.5-coder-32b-instruct:completion': 16,
- 'openrouter:raifle/sorcererlm-8x22b:prompt': 450,
- 'openrouter:raifle/sorcererlm-8x22b:completion': 450,
- 'openrouter:thedrummer/unslopnemo-12b:prompt': 40,
- 'openrouter:thedrummer/unslopnemo-12b:completion': 40,
- 'openrouter:anthropic/claude-3.5-haiku:prompt': 80,
- 'openrouter:anthropic/claude-3.5-haiku:completion': 400,
- 'openrouter:anthropic/claude-3.5-haiku:web_search': 1000000,
- 'openrouter:anthropic/claude-3.5-haiku:input_cache_read': 8,
- 'openrouter:anthropic/claude-3.5-haiku:input_cache_write': 100,
- 'openrouter:anthropic/claude-3.5-haiku-20241022:prompt': 80,
- 'openrouter:anthropic/claude-3.5-haiku-20241022:completion': 400,
- 'openrouter:anthropic/claude-3.5-haiku-20241022:input_cache_read': 8,
- 'openrouter:anthropic/claude-3.5-haiku-20241022:input_cache_write': 100,
- 'openrouter:anthracite-org/magnum-v4-72b:prompt': 250,
- 'openrouter:anthracite-org/magnum-v4-72b:completion': 500,
- 'openrouter:anthropic/claude-3.5-sonnet:prompt': 300,
- 'openrouter:anthropic/claude-3.5-sonnet:completion': 1500,
- 'openrouter:anthropic/claude-3.5-sonnet:image': 480000,
- 'openrouter:anthropic/claude-3.5-sonnet:input_cache_read': 30,
- 'openrouter:anthropic/claude-3.5-sonnet:input_cache_write': 375,
- 'openrouter:mistralai/ministral-3b:prompt': 4,
- 'openrouter:mistralai/ministral-3b:completion': 4,
- 'openrouter:mistralai/ministral-8b:prompt': 10,
- 'openrouter:mistralai/ministral-8b:completion': 10,
- 'openrouter:qwen/qwen-2.5-7b-instruct:prompt': 4,
- 'openrouter:qwen/qwen-2.5-7b-instruct:completion': 10,
- 'openrouter:nvidia/llama-3.1-nemotron-70b-instruct:prompt': 60,
- 'openrouter:nvidia/llama-3.1-nemotron-70b-instruct:completion': 60,
- 'openrouter:inflection/inflection-3-productivity:prompt': 250,
- 'openrouter:inflection/inflection-3-productivity:completion': 1000,
- 'openrouter:inflection/inflection-3-pi:prompt': 250,
- 'openrouter:inflection/inflection-3-pi:completion': 1000,
- 'openrouter:thedrummer/rocinante-12b:prompt': 17,
- 'openrouter:thedrummer/rocinante-12b:completion': 43,
- 'openrouter:anthracite-org/magnum-v2-72b:prompt': 300,
- 'openrouter:anthracite-org/magnum-v2-72b:completion': 300,
- 'openrouter:meta-llama/llama-3.2-3b-instruct:prompt': 2,
- 'openrouter:meta-llama/llama-3.2-3b-instruct:completion': 2,
- 'openrouter:meta-llama/llama-3.2-1b-instruct:prompt': 1,
- 'openrouter:meta-llama/llama-3.2-1b-instruct:completion': 1,
- 'openrouter:meta-llama/llama-3.2-11b-vision-instruct:prompt': 5,
- 'openrouter:meta-llama/llama-3.2-11b-vision-instruct:completion': 5,
- 'openrouter:meta-llama/llama-3.2-11b-vision-instruct:image': 7948,
- 'openrouter:meta-llama/llama-3.2-90b-vision-instruct:prompt': 35,
- 'openrouter:meta-llama/llama-3.2-90b-vision-instruct:completion': 40,
- 'openrouter:meta-llama/llama-3.2-90b-vision-instruct:image': 50580,
- 'openrouter:qwen/qwen-2.5-72b-instruct:prompt': 7,
- 'openrouter:qwen/qwen-2.5-72b-instruct:completion': 26,
- 'openrouter:neversleep/llama-3.1-lumimaid-8b:prompt': 9,
- 'openrouter:neversleep/llama-3.1-lumimaid-8b:completion': 60,
- 'openrouter:openai/o1-mini-2024-09-12:prompt': 110,
- 'openrouter:openai/o1-mini-2024-09-12:completion': 440,
- 'openrouter:openai/o1-mini-2024-09-12:input_cache_read': 55,
- 'openrouter:openai/o1-mini:prompt': 110,
- 'openrouter:openai/o1-mini:completion': 440,
- 'openrouter:openai/o1-mini:input_cache_read': 55,
- 'openrouter:mistralai/pixtral-12b:prompt': 10,
- 'openrouter:mistralai/pixtral-12b:completion': 10,
- 'openrouter:mistralai/pixtral-12b:image': 14450,
- 'openrouter:cohere/command-r-08-2024:prompt': 15,
- 'openrouter:cohere/command-r-08-2024:completion': 60,
- 'openrouter:cohere/command-r-plus-08-2024:prompt': 250,
- 'openrouter:cohere/command-r-plus-08-2024:completion': 1000,
- 'openrouter:sao10k/l3.1-euryale-70b:prompt': 65,
- 'openrouter:sao10k/l3.1-euryale-70b:completion': 75,
- 'openrouter:qwen/qwen-2.5-vl-7b-instruct:prompt': 20,
- 'openrouter:qwen/qwen-2.5-vl-7b-instruct:completion': 20,
- 'openrouter:qwen/qwen-2.5-vl-7b-instruct:image': 14450,
- 'openrouter:microsoft/phi-3.5-mini-128k-instruct:prompt': 10,
- 'openrouter:microsoft/phi-3.5-mini-128k-instruct:completion': 10,
- 'openrouter:nousresearch/hermes-3-llama-3.1-70b:prompt': 30,
- 'openrouter:nousresearch/hermes-3-llama-3.1-70b:completion': 30,
- 'openrouter:nousresearch/hermes-3-llama-3.1-405b:prompt': 100,
- 'openrouter:nousresearch/hermes-3-llama-3.1-405b:completion': 100,
- 'openrouter:openai/chatgpt-4o-latest:prompt': 500,
- 'openrouter:openai/chatgpt-4o-latest:completion': 1500,
- 'openrouter:openai/chatgpt-4o-latest:image': 722500,
- 'openrouter:sao10k/l3-lunaris-8b:prompt': 4,
- 'openrouter:sao10k/l3-lunaris-8b:completion': 5,
- 'openrouter:openai/gpt-4o-2024-08-06:prompt': 250,
- 'openrouter:openai/gpt-4o-2024-08-06:completion': 1000,
- 'openrouter:openai/gpt-4o-2024-08-06:image': 361300,
- 'openrouter:openai/gpt-4o-2024-08-06:input_cache_read': 125,
- 'openrouter:meta-llama/llama-3.1-405b:prompt': 400,
- 'openrouter:meta-llama/llama-3.1-405b:completion': 400,
- 'openrouter:meta-llama/llama-3.1-8b-instruct:prompt': 2,
- 'openrouter:meta-llama/llama-3.1-8b-instruct:completion': 3,
- 'openrouter:meta-llama/llama-3.1-70b-instruct:prompt': 40,
- 'openrouter:meta-llama/llama-3.1-70b-instruct:completion': 40,
- 'openrouter:meta-llama/llama-3.1-405b-instruct:prompt': 80,
- 'openrouter:meta-llama/llama-3.1-405b-instruct:completion': 80,
- 'openrouter:mistralai/mistral-nemo:prompt': 2,
- 'openrouter:mistralai/mistral-nemo:completion': 4,
- 'openrouter:openai/gpt-4o-mini-2024-07-18:prompt': 15,
- 'openrouter:openai/gpt-4o-mini-2024-07-18:completion': 60,
- 'openrouter:openai/gpt-4o-mini-2024-07-18:image': 722500,
- 'openrouter:openai/gpt-4o-mini-2024-07-18:input_cache_read': 7,
- 'openrouter:openai/gpt-4o-mini:prompt': 15,
- 'openrouter:openai/gpt-4o-mini:completion': 60,
- 'openrouter:openai/gpt-4o-mini:image': 21700,
- 'openrouter:openai/gpt-4o-mini:input_cache_read': 7,
- 'openrouter:google/gemma-2-27b-it:prompt': 65,
- 'openrouter:google/gemma-2-27b-it:completion': 65,
- 'openrouter:google/gemma-2-9b-it:prompt': 1,
- 'openrouter:google/gemma-2-9b-it:completion': 3,
- 'openrouter:anthropic/claude-3.5-sonnet-20240620:prompt': 300,
- 'openrouter:anthropic/claude-3.5-sonnet-20240620:completion': 1500,
- 'openrouter:anthropic/claude-3.5-sonnet-20240620:image': 480000,
- 'openrouter:anthropic/claude-3.5-sonnet-20240620:input_cache_read': 30,
- 'openrouter:anthropic/claude-3.5-sonnet-20240620:input_cache_write': 375,
- 'openrouter:sao10k/l3-euryale-70b:prompt': 148,
- 'openrouter:sao10k/l3-euryale-70b:completion': 148,
- 'openrouter:mistralai/mistral-7b-instruct-v0.3:prompt': 3,
- 'openrouter:mistralai/mistral-7b-instruct-v0.3:completion': 5,
- 'openrouter:mistralai/mistral-7b-instruct:prompt': 3,
- 'openrouter:mistralai/mistral-7b-instruct:completion': 5,
- 'openrouter:nousresearch/hermes-2-pro-llama-3-8b:prompt': 3,
- 'openrouter:nousresearch/hermes-2-pro-llama-3-8b:completion': 8,
- 'openrouter:microsoft/phi-3-mini-128k-instruct:prompt': 10,
- 'openrouter:microsoft/phi-3-mini-128k-instruct:completion': 10,
- 'openrouter:microsoft/phi-3-medium-128k-instruct:prompt': 100,
- 'openrouter:microsoft/phi-3-medium-128k-instruct:completion': 100,
- 'openrouter:openai/gpt-4o-2024-05-13:prompt': 500,
- 'openrouter:openai/gpt-4o-2024-05-13:completion': 1500,
- 'openrouter:openai/gpt-4o-2024-05-13:image': 722500,
- 'openrouter:openai/gpt-4o:prompt': 250,
- 'openrouter:openai/gpt-4o:completion': 1000,
- 'openrouter:openai/gpt-4o:image': 361300,
- 'openrouter:openai/gpt-4o:input_cache_read': 125,
- 'openrouter:openai/gpt-4o:extended:prompt': 600,
- 'openrouter:openai/gpt-4o:extended:completion': 1800,
- 'openrouter:openai/gpt-4o:extended:image': 722500,
- 'openrouter:meta-llama/llama-guard-2-8b:prompt': 20,
- 'openrouter:meta-llama/llama-guard-2-8b:completion': 20,
- 'openrouter:meta-llama/llama-3-8b-instruct:prompt': 3,
- 'openrouter:meta-llama/llama-3-8b-instruct:completion': 6,
- 'openrouter:meta-llama/llama-3-70b-instruct:prompt': 30,
- 'openrouter:meta-llama/llama-3-70b-instruct:completion': 40,
- 'openrouter:mistralai/mixtral-8x22b-instruct:prompt': 90,
- 'openrouter:mistralai/mixtral-8x22b-instruct:completion': 90,
- 'openrouter:microsoft/wizardlm-2-8x22b:prompt': 48,
- 'openrouter:microsoft/wizardlm-2-8x22b:completion': 48,
- 'openrouter:openai/gpt-4-turbo:prompt': 1000,
- 'openrouter:openai/gpt-4-turbo:completion': 3000,
- 'openrouter:openai/gpt-4-turbo:image': 1445000,
- 'openrouter:anthropic/claude-3-haiku:prompt': 25,
- 'openrouter:anthropic/claude-3-haiku:completion': 125,
- 'openrouter:anthropic/claude-3-haiku:image': 40000,
- 'openrouter:anthropic/claude-3-haiku:input_cache_read': 3,
- 'openrouter:anthropic/claude-3-haiku:input_cache_write': 30,
- 'openrouter:anthropic/claude-3-opus:prompt': 1500,
- 'openrouter:anthropic/claude-3-opus:completion': 7500,
- 'openrouter:anthropic/claude-3-opus:image': 2400000,
- 'openrouter:anthropic/claude-3-opus:input_cache_read': 150,
- 'openrouter:anthropic/claude-3-opus:input_cache_write': 1875,
- 'openrouter:mistralai/mistral-large:prompt': 200,
- 'openrouter:mistralai/mistral-large:completion': 600,
- 'openrouter:openai/gpt-4-turbo-preview:prompt': 1000,
- 'openrouter:openai/gpt-4-turbo-preview:completion': 3000,
- 'openrouter:openai/gpt-3.5-turbo-0613:prompt': 100,
- 'openrouter:openai/gpt-3.5-turbo-0613:completion': 200,
- 'openrouter:mistralai/mistral-small:prompt': 20,
- 'openrouter:mistralai/mistral-small:completion': 60,
- 'openrouter:mistralai/mistral-tiny:prompt': 25,
- 'openrouter:mistralai/mistral-tiny:completion': 25,
- 'openrouter:mistralai/mistral-7b-instruct-v0.2:prompt': 20,
- 'openrouter:mistralai/mistral-7b-instruct-v0.2:completion': 20,
- 'openrouter:mistralai/mixtral-8x7b-instruct:prompt': 54,
- 'openrouter:mistralai/mixtral-8x7b-instruct:completion': 54,
- 'openrouter:neversleep/noromaid-20b:prompt': 100,
- 'openrouter:neversleep/noromaid-20b:completion': 175,
- 'openrouter:alpindale/goliath-120b:prompt': 400,
- 'openrouter:alpindale/goliath-120b:completion': 550,
- 'openrouter:openrouter/auto:prompt': -100000000,
- 'openrouter:openrouter/auto:completion': -100000000,
- 'openrouter:openai/gpt-4-1106-preview:prompt': 1000,
- 'openrouter:openai/gpt-4-1106-preview:completion': 3000,
- 'openrouter:mistralai/mistral-7b-instruct-v0.1:prompt': 11,
- 'openrouter:mistralai/mistral-7b-instruct-v0.1:completion': 19,
- 'openrouter:openai/gpt-3.5-turbo-instruct:prompt': 150,
- 'openrouter:openai/gpt-3.5-turbo-instruct:completion': 200,
- 'openrouter:openai/gpt-3.5-turbo-16k:prompt': 300,
- 'openrouter:openai/gpt-3.5-turbo-16k:completion': 400,
- 'openrouter:mancer/weaver:prompt': 113,
- 'openrouter:mancer/weaver:completion': 113,
- 'openrouter:undi95/remm-slerp-l2-13b:prompt': 45,
- 'openrouter:undi95/remm-slerp-l2-13b:completion': 65,
- 'openrouter:gryphe/mythomax-l2-13b:prompt': 5,
- 'openrouter:gryphe/mythomax-l2-13b:completion': 9,
- 'openrouter:openai/gpt-4:prompt': 3000,
- 'openrouter:openai/gpt-4:completion': 6000,
- 'openrouter:openai/gpt-3.5-turbo:prompt': 50,
- 'openrouter:openai/gpt-3.5-turbo:completion': 150,
- 'openrouter:openai/gpt-4-0314:prompt': 3000,
- 'openrouter:openai/gpt-4-0314:completion': 6000,
+ "openrouter:anthropic/claude-haiku-4.5:prompt": 100,
+ "openrouter:anthropic/claude-haiku-4.5:completion": 500,
+ "openrouter:anthropic/claude-haiku-4.5:input_cache_read": 10,
+ "openrouter:anthropic/claude-haiku-4.5:input_cache_write": 125,
+ "openrouter:qwen/qwen3-vl-8b-thinking:prompt": 18,
+ "openrouter:qwen/qwen3-vl-8b-thinking:completion": 210,
+ "openrouter:qwen/qwen3-vl-8b-instruct:prompt": 18,
+ "openrouter:qwen/qwen3-vl-8b-instruct:completion": 69,
+ "openrouter:inclusionai/ling-1t:prompt": 100,
+ "openrouter:inclusionai/ling-1t:completion": 300,
+ "openrouter:openai/o3-deep-research:prompt": 1000,
+ "openrouter:openai/o3-deep-research:completion": 4000,
+ "openrouter:openai/o3-deep-research:image": 765000,
+ "openrouter:openai/o3-deep-research:web_search": 1000000,
+ "openrouter:openai/o3-deep-research:input_cache_read": 250,
+ "openrouter:openai/o4-mini-deep-research:prompt": 200,
+ "openrouter:openai/o4-mini-deep-research:completion": 800,
+ "openrouter:openai/o4-mini-deep-research:image": 153000,
+ "openrouter:openai/o4-mini-deep-research:web_search": 1000000,
+ "openrouter:openai/o4-mini-deep-research:input_cache_read": 50,
+ "openrouter:nvidia/llama-3.3-nemotron-super-49b-v1.5:prompt": 10,
+ "openrouter:nvidia/llama-3.3-nemotron-super-49b-v1.5:completion": 40,
+ "openrouter:baidu/ernie-4.5-21b-a3b-thinking:prompt": 7,
+ "openrouter:baidu/ernie-4.5-21b-a3b-thinking:completion": 28,
+ "openrouter:google/gemini-2.5-flash-image:prompt": 30,
+ "openrouter:google/gemini-2.5-flash-image:completion": 250,
+ "openrouter:google/gemini-2.5-flash-image:image": 123800,
+ "openrouter:qwen/qwen3-vl-30b-a3b-thinking:prompt": 29,
+ "openrouter:qwen/qwen3-vl-30b-a3b-thinking:completion": 100,
+ "openrouter:qwen/qwen3-vl-30b-a3b-instruct:prompt": 29,
+ "openrouter:qwen/qwen3-vl-30b-a3b-instruct:completion": 99,
+ "openrouter:openai/gpt-5-pro:prompt": 1500,
+ "openrouter:openai/gpt-5-pro:completion": 12000,
+ "openrouter:z-ai/glm-4.6:prompt": 50,
+ "openrouter:z-ai/glm-4.6:completion": 175,
+ "openrouter:anthropic/claude-sonnet-4.5:prompt": 300,
+ "openrouter:anthropic/claude-sonnet-4.5:completion": 1500,
+ "openrouter:deepseek/deepseek-v3.2-exp:prompt": 27,
+ "openrouter:deepseek/deepseek-v3.2-exp:completion": 40,
+ "openrouter:thedrummer/cydonia-24b-v4.1:prompt": 30,
+ "openrouter:thedrummer/cydonia-24b-v4.1:completion": 50,
+ "openrouter:relace/relace-apply-3:prompt": 85,
+ "openrouter:relace/relace-apply-3:completion": 125,
+ "openrouter:google/gemini-2.5-flash-preview-09-2025:prompt": 30,
+ "openrouter:google/gemini-2.5-flash-preview-09-2025:completion": 250,
+ "openrouter:google/gemini-2.5-flash-preview-09-2025:image": 123800,
+ "openrouter:google/gemini-2.5-flash-preview-09-2025:input_cache_read": 7,
+ "openrouter:google/gemini-2.5-flash-preview-09-2025:input_cache_write": 38,
+ "openrouter:google/gemini-2.5-flash-lite-preview-09-2025:prompt": 10,
+ "openrouter:google/gemini-2.5-flash-lite-preview-09-2025:completion": 40,
+ "openrouter:qwen/qwen3-vl-235b-a22b-thinking:prompt": 45,
+ "openrouter:qwen/qwen3-vl-235b-a22b-thinking:completion": 350,
+ "openrouter:qwen/qwen3-vl-235b-a22b-instruct:prompt": 30,
+ "openrouter:qwen/qwen3-vl-235b-a22b-instruct:completion": 120,
+ "openrouter:qwen/qwen3-max:prompt": 120,
+ "openrouter:qwen/qwen3-max:completion": 600,
+ "openrouter:qwen/qwen3-max:input_cache_read": 24,
+ "openrouter:qwen/qwen3-coder-plus:prompt": 100,
+ "openrouter:qwen/qwen3-coder-plus:completion": 500,
+ "openrouter:qwen/qwen3-coder-plus:input_cache_read": 10,
+ "openrouter:openai/gpt-5-codex:prompt": 125,
+ "openrouter:openai/gpt-5-codex:completion": 1000,
+ "openrouter:openai/gpt-5-codex:input_cache_read": 12,
+ "openrouter:deepseek/deepseek-v3.1-terminus:prompt": 23,
+ "openrouter:deepseek/deepseek-v3.1-terminus:completion": 90,
+ "openrouter:x-ai/grok-4-fast:prompt": 20,
+ "openrouter:x-ai/grok-4-fast:completion": 50,
+ "openrouter:x-ai/grok-4-fast:input_cache_read": 5,
+ "openrouter:alibaba/tongyi-deepresearch-30b-a3b:prompt": 9,
+ "openrouter:alibaba/tongyi-deepresearch-30b-a3b:completion": 40,
+ "openrouter:qwen/qwen3-coder-flash:prompt": 30,
+ "openrouter:qwen/qwen3-coder-flash:completion": 150,
+ "openrouter:qwen/qwen3-coder-flash:input_cache_read": 8,
+ "openrouter:arcee-ai/afm-4.5b:prompt": 5,
+ "openrouter:arcee-ai/afm-4.5b:completion": 15,
+ "openrouter:opengvlab/internvl3-78b:prompt": 7,
+ "openrouter:opengvlab/internvl3-78b:completion": 26,
+ "openrouter:qwen/qwen3-next-80b-a3b-thinking:prompt": 14,
+ "openrouter:qwen/qwen3-next-80b-a3b-thinking:completion": 120,
+ "openrouter:qwen/qwen3-next-80b-a3b-instruct:prompt": 10,
+ "openrouter:qwen/qwen3-next-80b-a3b-instruct:completion": 80,
+ "openrouter:meituan/longcat-flash-chat:prompt": 15,
+ "openrouter:meituan/longcat-flash-chat:completion": 75,
+ "openrouter:qwen/qwen-plus-2025-07-28:prompt": 40,
+ "openrouter:qwen/qwen-plus-2025-07-28:completion": 120,
+ "openrouter:qwen/qwen-plus-2025-07-28:thinking:prompt": 40,
+ "openrouter:qwen/qwen-plus-2025-07-28:thinking:completion": 400,
+ "openrouter:nvidia/nemotron-nano-9b-v2:prompt": 4,
+ "openrouter:nvidia/nemotron-nano-9b-v2:completion": 16,
+ "openrouter:moonshotai/kimi-k2-0905:prompt": 39,
+ "openrouter:moonshotai/kimi-k2-0905:completion": 190,
+ "openrouter:deepcogito/cogito-v2-preview-llama-109b-moe:prompt": 18,
+ "openrouter:deepcogito/cogito-v2-preview-llama-109b-moe:completion": 59,
+ "openrouter:deepcogito/cogito-v2-preview-deepseek-671b:prompt": 125,
+ "openrouter:deepcogito/cogito-v2-preview-deepseek-671b:completion": 125,
+ "openrouter:stepfun-ai/step3:prompt": 57,
+ "openrouter:stepfun-ai/step3:completion": 142,
+ "openrouter:qwen/qwen3-30b-a3b-thinking-2507:prompt": 8,
+ "openrouter:qwen/qwen3-30b-a3b-thinking-2507:completion": 29,
+ "openrouter:x-ai/grok-code-fast-1:prompt": 20,
+ "openrouter:x-ai/grok-code-fast-1:completion": 150,
+ "openrouter:x-ai/grok-code-fast-1:input_cache_read": 2,
+ "openrouter:nousresearch/hermes-4-70b:prompt": 11,
+ "openrouter:nousresearch/hermes-4-70b:completion": 38,
+ "openrouter:nousresearch/hermes-4-405b:prompt": 30,
+ "openrouter:nousresearch/hermes-4-405b:completion": 120,
+ "openrouter:google/gemini-2.5-flash-image-preview:prompt": 30,
+ "openrouter:google/gemini-2.5-flash-image-preview:completion": 250,
+ "openrouter:google/gemini-2.5-flash-image-preview:image": 123800,
+ "openrouter:deepseek/deepseek-chat-v3.1:prompt": 20,
+ "openrouter:deepseek/deepseek-chat-v3.1:completion": 80,
+ "openrouter:openai/gpt-4o-audio-preview:prompt": 250,
+ "openrouter:openai/gpt-4o-audio-preview:completion": 1000,
+ "openrouter:openai/gpt-4o-audio-preview:audio": 4000,
+ "openrouter:mistralai/mistral-medium-3.1:prompt": 40,
+ "openrouter:mistralai/mistral-medium-3.1:completion": 200,
+ "openrouter:baidu/ernie-4.5-21b-a3b:prompt": 7,
+ "openrouter:baidu/ernie-4.5-21b-a3b:completion": 28,
+ "openrouter:baidu/ernie-4.5-vl-28b-a3b:prompt": 14,
+ "openrouter:baidu/ernie-4.5-vl-28b-a3b:completion": 56,
+ "openrouter:z-ai/glm-4.5v:prompt": 60,
+ "openrouter:z-ai/glm-4.5v:completion": 180,
+ "openrouter:z-ai/glm-4.5v:input_cache_read": 11,
+ "openrouter:ai21/jamba-mini-1.7:prompt": 20,
+ "openrouter:ai21/jamba-mini-1.7:completion": 40,
+ "openrouter:ai21/jamba-large-1.7:prompt": 200,
+ "openrouter:ai21/jamba-large-1.7:completion": 800,
+ "openrouter:openai/gpt-5-chat:prompt": 125,
+ "openrouter:openai/gpt-5-chat:completion": 1000,
+ "openrouter:openai/gpt-5-chat:input_cache_read": 12,
+ "openrouter:openai/gpt-5:prompt": 125,
+ "openrouter:openai/gpt-5:completion": 1000,
+ "openrouter:openai/gpt-5:web_search": 1000000,
+ "openrouter:openai/gpt-5:input_cache_read": 12,
+ "openrouter:openai/gpt-5-mini:prompt": 25,
+ "openrouter:openai/gpt-5-mini:completion": 200,
+ "openrouter:openai/gpt-5-mini:web_search": 1000000,
+ "openrouter:openai/gpt-5-mini:input_cache_read": 3,
+ "openrouter:openai/gpt-5-nano:prompt": 5,
+ "openrouter:openai/gpt-5-nano:completion": 40,
+ "openrouter:openai/gpt-5-nano:web_search": 1000000,
+ "openrouter:openai/gpt-5-nano:input_cache_read": 1,
+ "openrouter:openai/gpt-oss-120b:prompt": 4,
+ "openrouter:openai/gpt-oss-120b:completion": 40,
+ "openrouter:openai/gpt-oss-20b:prompt": 3,
+ "openrouter:openai/gpt-oss-20b:completion": 14,
+ "openrouter:anthropic/claude-opus-4.1:prompt": 1500,
+ "openrouter:anthropic/claude-opus-4.1:completion": 7500,
+ "openrouter:anthropic/claude-opus-4.1:image": 2400000,
+ "openrouter:anthropic/claude-opus-4.1:input_cache_read": 150,
+ "openrouter:anthropic/claude-opus-4.1:input_cache_write": 1875,
+ "openrouter:mistralai/codestral-2508:prompt": 30,
+ "openrouter:mistralai/codestral-2508:completion": 90,
+ "openrouter:qwen/qwen3-coder-30b-a3b-instruct:prompt": 6,
+ "openrouter:qwen/qwen3-coder-30b-a3b-instruct:completion": 25,
+ "openrouter:qwen/qwen3-30b-a3b-instruct-2507:prompt": 8,
+ "openrouter:qwen/qwen3-30b-a3b-instruct-2507:completion": 33,
+ "openrouter:z-ai/glm-4.5:prompt": 35,
+ "openrouter:z-ai/glm-4.5:completion": 155,
+ "openrouter:z-ai/glm-4.5-air:prompt": 14,
+ "openrouter:z-ai/glm-4.5-air:completion": 86,
+ "openrouter:qwen/qwen3-235b-a22b-thinking-2507:prompt": 11,
+ "openrouter:qwen/qwen3-235b-a22b-thinking-2507:completion": 60,
+ "openrouter:z-ai/glm-4-32b:prompt": 10,
+ "openrouter:z-ai/glm-4-32b:completion": 10,
+ "openrouter:qwen/qwen3-coder:prompt": 22,
+ "openrouter:qwen/qwen3-coder:completion": 95,
+ "openrouter:bytedance/ui-tars-1.5-7b:prompt": 10,
+ "openrouter:bytedance/ui-tars-1.5-7b:completion": 20,
+ "openrouter:google/gemini-2.5-flash-lite:prompt": 10,
+ "openrouter:google/gemini-2.5-flash-lite:completion": 40,
+ "openrouter:google/gemini-2.5-flash-lite:input_cache_read": 1,
+ "openrouter:google/gemini-2.5-flash-lite:input_cache_write": 18,
+ "openrouter:qwen/qwen3-235b-a22b-2507:prompt": 8,
+ "openrouter:qwen/qwen3-235b-a22b-2507:completion": 55,
+ "openrouter:switchpoint/router:prompt": 85,
+ "openrouter:switchpoint/router:completion": 340,
+ "openrouter:moonshotai/kimi-k2:prompt": 14,
+ "openrouter:moonshotai/kimi-k2:completion": 249,
+ "openrouter:thudm/glm-4.1v-9b-thinking:prompt": 4,
+ "openrouter:thudm/glm-4.1v-9b-thinking:completion": 14,
+ "openrouter:mistralai/devstral-medium:prompt": 40,
+ "openrouter:mistralai/devstral-medium:completion": 200,
+ "openrouter:mistralai/devstral-small:prompt": 7,
+ "openrouter:mistralai/devstral-small:completion": 28,
+ "openrouter:x-ai/grok-4:prompt": 300,
+ "openrouter:x-ai/grok-4:completion": 1500,
+ "openrouter:x-ai/grok-4:input_cache_read": 75,
+ "openrouter:tencent/hunyuan-a13b-instruct:prompt": 3,
+ "openrouter:tencent/hunyuan-a13b-instruct:completion": 3,
+ "openrouter:tngtech/deepseek-r1t2-chimera:prompt": 30,
+ "openrouter:tngtech/deepseek-r1t2-chimera:completion": 120,
+ "openrouter:morph/morph-v3-large:prompt": 90,
+ "openrouter:morph/morph-v3-large:completion": 190,
+ "openrouter:morph/morph-v3-fast:prompt": 80,
+ "openrouter:morph/morph-v3-fast:completion": 120,
+ "openrouter:baidu/ernie-4.5-vl-424b-a47b:prompt": 42,
+ "openrouter:baidu/ernie-4.5-vl-424b-a47b:completion": 125,
+ "openrouter:baidu/ernie-4.5-300b-a47b:prompt": 28,
+ "openrouter:baidu/ernie-4.5-300b-a47b:completion": 110,
+ "openrouter:thedrummer/anubis-70b-v1.1:prompt": 65,
+ "openrouter:thedrummer/anubis-70b-v1.1:completion": 100,
+ "openrouter:inception/mercury:prompt": 25,
+ "openrouter:inception/mercury:completion": 100,
+ "openrouter:mistralai/mistral-small-3.2-24b-instruct:prompt": 6,
+ "openrouter:mistralai/mistral-small-3.2-24b-instruct:completion": 18,
+ "openrouter:minimax/minimax-m1:prompt": 40,
+ "openrouter:minimax/minimax-m1:completion": 220,
+ "openrouter:google/gemini-2.5-flash-lite-preview-06-17:prompt": 10,
+ "openrouter:google/gemini-2.5-flash-lite-preview-06-17:completion": 40,
+ "openrouter:google/gemini-2.5-flash-lite-preview-06-17:audio": 30,
+ "openrouter:google/gemini-2.5-flash-lite-preview-06-17:input_cache_read": 3,
+ "openrouter:google/gemini-2.5-flash-lite-preview-06-17:input_cache_write": 18,
+ "openrouter:google/gemini-2.5-flash:prompt": 30,
+ "openrouter:google/gemini-2.5-flash:completion": 250,
+ "openrouter:google/gemini-2.5-flash:image": 123800,
+ "openrouter:google/gemini-2.5-flash:input_cache_read": 3,
+ "openrouter:google/gemini-2.5-flash:input_cache_write": 38,
+ "openrouter:google/gemini-2.5-pro:prompt": 125,
+ "openrouter:google/gemini-2.5-pro:completion": 1000,
+ "openrouter:google/gemini-2.5-pro:image": 516000,
+ "openrouter:google/gemini-2.5-pro:input_cache_read": 12,
+ "openrouter:google/gemini-2.5-pro:input_cache_write": 163,
+ "openrouter:moonshotai/kimi-dev-72b:prompt": 29,
+ "openrouter:moonshotai/kimi-dev-72b:completion": 115,
+ "openrouter:openai/o3-pro:prompt": 2000,
+ "openrouter:openai/o3-pro:completion": 8000,
+ "openrouter:openai/o3-pro:image": 1530000,
+ "openrouter:openai/o3-pro:web_search": 1000000,
+ "openrouter:x-ai/grok-3-mini:prompt": 30,
+ "openrouter:x-ai/grok-3-mini:completion": 50,
+ "openrouter:x-ai/grok-3-mini:input_cache_read": 7,
+ "openrouter:x-ai/grok-3:prompt": 300,
+ "openrouter:x-ai/grok-3:completion": 1500,
+ "openrouter:x-ai/grok-3:input_cache_read": 75,
+ "openrouter:mistralai/magistral-small-2506:prompt": 50,
+ "openrouter:mistralai/magistral-small-2506:completion": 150,
+ "openrouter:mistralai/magistral-medium-2506:prompt": 200,
+ "openrouter:mistralai/magistral-medium-2506:completion": 500,
+ "openrouter:mistralai/magistral-medium-2506:thinking:prompt": 200,
+ "openrouter:mistralai/magistral-medium-2506:thinking:completion": 500,
+ "openrouter:google/gemini-2.5-pro-preview:prompt": 125,
+ "openrouter:google/gemini-2.5-pro-preview:completion": 1000,
+ "openrouter:google/gemini-2.5-pro-preview:image": 516000,
+ "openrouter:google/gemini-2.5-pro-preview:input_cache_read": 31,
+ "openrouter:google/gemini-2.5-pro-preview:input_cache_write": 163,
+ "openrouter:deepseek/deepseek-r1-0528-qwen3-8b:prompt": 3,
+ "openrouter:deepseek/deepseek-r1-0528-qwen3-8b:completion": 11,
+ "openrouter:deepseek/deepseek-r1-0528:prompt": 40,
+ "openrouter:deepseek/deepseek-r1-0528:completion": 175,
+ "openrouter:anthropic/claude-opus-4:prompt": 1500,
+ "openrouter:anthropic/claude-opus-4:completion": 7500,
+ "openrouter:anthropic/claude-opus-4:image": 2400000,
+ "openrouter:anthropic/claude-opus-4:input_cache_read": 150,
+ "openrouter:anthropic/claude-opus-4:input_cache_write": 1875,
+ "openrouter:anthropic/claude-sonnet-4:prompt": 300,
+ "openrouter:anthropic/claude-sonnet-4:completion": 1500,
+ "openrouter:anthropic/claude-sonnet-4:image": 480000,
+ "openrouter:anthropic/claude-sonnet-4:input_cache_read": 30,
+ "openrouter:anthropic/claude-sonnet-4:input_cache_write": 375,
+ "openrouter:mistralai/devstral-small-2505:prompt": 5,
+ "openrouter:mistralai/devstral-small-2505:completion": 22,
+ "openrouter:google/gemma-3n-e4b-it:prompt": 2,
+ "openrouter:google/gemma-3n-e4b-it:completion": 4,
+ "openrouter:openai/codex-mini:prompt": 150,
+ "openrouter:openai/codex-mini:completion": 600,
+ "openrouter:openai/codex-mini:input_cache_read": 38,
+ "openrouter:nousresearch/deephermes-3-mistral-24b-preview:prompt": 15,
+ "openrouter:nousresearch/deephermes-3-mistral-24b-preview:completion": 59,
+ "openrouter:mistralai/mistral-medium-3:prompt": 40,
+ "openrouter:mistralai/mistral-medium-3:completion": 200,
+ "openrouter:google/gemini-2.5-pro-preview-05-06:prompt": 125,
+ "openrouter:google/gemini-2.5-pro-preview-05-06:completion": 1000,
+ "openrouter:google/gemini-2.5-pro-preview-05-06:image": 516000,
+ "openrouter:google/gemini-2.5-pro-preview-05-06:input_cache_read": 31,
+ "openrouter:google/gemini-2.5-pro-preview-05-06:input_cache_write": 163,
+ "openrouter:arcee-ai/spotlight:prompt": 18,
+ "openrouter:arcee-ai/spotlight:completion": 18,
+ "openrouter:arcee-ai/maestro-reasoning:prompt": 90,
+ "openrouter:arcee-ai/maestro-reasoning:completion": 330,
+ "openrouter:arcee-ai/virtuoso-large:prompt": 75,
+ "openrouter:arcee-ai/virtuoso-large:completion": 120,
+ "openrouter:arcee-ai/coder-large:prompt": 50,
+ "openrouter:arcee-ai/coder-large:completion": 80,
+ "openrouter:microsoft/phi-4-reasoning-plus:prompt": 7,
+ "openrouter:microsoft/phi-4-reasoning-plus:completion": 35,
+ "openrouter:inception/mercury-coder:prompt": 25,
+ "openrouter:inception/mercury-coder:completion": 100,
+ "openrouter:deepseek/deepseek-prover-v2:prompt": 50,
+ "openrouter:deepseek/deepseek-prover-v2:completion": 218,
+ "openrouter:meta-llama/llama-guard-4-12b:prompt": 18,
+ "openrouter:meta-llama/llama-guard-4-12b:completion": 18,
+ "openrouter:qwen/qwen3-30b-a3b:prompt": 6,
+ "openrouter:qwen/qwen3-30b-a3b:completion": 22,
+ "openrouter:qwen/qwen3-8b:prompt": 4,
+ "openrouter:qwen/qwen3-8b:completion": 14,
+ "openrouter:qwen/qwen3-14b:prompt": 5,
+ "openrouter:qwen/qwen3-14b:completion": 22,
+ "openrouter:qwen/qwen3-32b:prompt": 5,
+ "openrouter:qwen/qwen3-32b:completion": 20,
+ "openrouter:qwen/qwen3-235b-a22b:prompt": 18,
+ "openrouter:qwen/qwen3-235b-a22b:completion": 54,
+ "openrouter:tngtech/deepseek-r1t-chimera:prompt": 30,
+ "openrouter:tngtech/deepseek-r1t-chimera:completion": 120,
+ "openrouter:microsoft/mai-ds-r1:prompt": 30,
+ "openrouter:microsoft/mai-ds-r1:completion": 120,
+ "openrouter:thudm/glm-z1-32b:prompt": 5,
+ "openrouter:thudm/glm-z1-32b:completion": 22,
+ "openrouter:openai/o4-mini-high:prompt": 110,
+ "openrouter:openai/o4-mini-high:completion": 440,
+ "openrouter:openai/o4-mini-high:image": 84150,
+ "openrouter:openai/o4-mini-high:web_search": 1000000,
+ "openrouter:openai/o4-mini-high:input_cache_read": 28,
+ "openrouter:openai/o3:prompt": 200,
+ "openrouter:openai/o3:completion": 800,
+ "openrouter:openai/o3:image": 153000,
+ "openrouter:openai/o3:web_search": 1000000,
+ "openrouter:openai/o3:input_cache_read": 50,
+ "openrouter:openai/o4-mini:prompt": 110,
+ "openrouter:openai/o4-mini:completion": 440,
+ "openrouter:openai/o4-mini:image": 84150,
+ "openrouter:openai/o4-mini:web_search": 1000000,
+ "openrouter:openai/o4-mini:input_cache_read": 28,
+ "openrouter:shisa-ai/shisa-v2-llama3.3-70b:prompt": 5,
+ "openrouter:shisa-ai/shisa-v2-llama3.3-70b:completion": 22,
+ "openrouter:qwen/qwen2.5-coder-7b-instruct:prompt": 3,
+ "openrouter:qwen/qwen2.5-coder-7b-instruct:completion": 9,
+ "openrouter:openai/gpt-4.1:prompt": 200,
+ "openrouter:openai/gpt-4.1:completion": 800,
+ "openrouter:openai/gpt-4.1:web_search": 1000000,
+ "openrouter:openai/gpt-4.1:input_cache_read": 50,
+ "openrouter:openai/gpt-4.1-mini:prompt": 40,
+ "openrouter:openai/gpt-4.1-mini:completion": 160,
+ "openrouter:openai/gpt-4.1-mini:web_search": 1000000,
+ "openrouter:openai/gpt-4.1-mini:input_cache_read": 10,
+ "openrouter:openai/gpt-4.1-nano:prompt": 10,
+ "openrouter:openai/gpt-4.1-nano:completion": 40,
+ "openrouter:openai/gpt-4.1-nano:web_search": 1000000,
+ "openrouter:openai/gpt-4.1-nano:input_cache_read": 3,
+ "openrouter:eleutherai/llemma_7b:prompt": 80,
+ "openrouter:eleutherai/llemma_7b:completion": 120,
+ "openrouter:alfredpros/codellama-7b-instruct-solidity:prompt": 80,
+ "openrouter:alfredpros/codellama-7b-instruct-solidity:completion": 120,
+ "openrouter:arliai/qwq-32b-arliai-rpr-v1:prompt": 3,
+ "openrouter:arliai/qwq-32b-arliai-rpr-v1:completion": 11,
+ "openrouter:agentica-org/deepcoder-14b-preview:prompt": 1,
+ "openrouter:agentica-org/deepcoder-14b-preview:completion": 1,
+ "openrouter:x-ai/grok-3-mini-beta:prompt": 30,
+ "openrouter:x-ai/grok-3-mini-beta:completion": 50,
+ "openrouter:x-ai/grok-3-mini-beta:input_cache_read": 7,
+ "openrouter:x-ai/grok-3-beta:prompt": 300,
+ "openrouter:x-ai/grok-3-beta:completion": 1500,
+ "openrouter:x-ai/grok-3-beta:input_cache_read": 75,
+ "openrouter:nvidia/llama-3.1-nemotron-ultra-253b-v1:prompt": 60,
+ "openrouter:nvidia/llama-3.1-nemotron-ultra-253b-v1:completion": 180,
+ "openrouter:meta-llama/llama-4-maverick:prompt": 15,
+ "openrouter:meta-llama/llama-4-maverick:completion": 60,
+ "openrouter:meta-llama/llama-4-maverick:image": 66840,
+ "openrouter:meta-llama/llama-4-scout:prompt": 8,
+ "openrouter:meta-llama/llama-4-scout:completion": 30,
+ "openrouter:meta-llama/llama-4-scout:image": 33420,
+ "openrouter:allenai/molmo-7b-d:prompt": 10,
+ "openrouter:allenai/molmo-7b-d:completion": 20,
+ "openrouter:qwen/qwen2.5-vl-32b-instruct:prompt": 5,
+ "openrouter:qwen/qwen2.5-vl-32b-instruct:completion": 22,
+ "openrouter:deepseek/deepseek-chat-v3-0324:prompt": 24,
+ "openrouter:deepseek/deepseek-chat-v3-0324:completion": 84,
+ "openrouter:openai/o1-pro:prompt": 15000,
+ "openrouter:openai/o1-pro:completion": 60000,
+ "openrouter:openai/o1-pro:image": 21675000,
+ "openrouter:mistralai/mistral-small-3.1-24b-instruct:prompt": 5,
+ "openrouter:mistralai/mistral-small-3.1-24b-instruct:completion": 10,
+ "openrouter:allenai/olmo-2-0325-32b-instruct:prompt": 20,
+ "openrouter:allenai/olmo-2-0325-32b-instruct:completion": 35,
+ "openrouter:google/gemma-3-4b-it:prompt": 2,
+ "openrouter:google/gemma-3-4b-it:completion": 7,
+ "openrouter:google/gemma-3-12b-it:prompt": 3,
+ "openrouter:google/gemma-3-12b-it:completion": 10,
+ "openrouter:cohere/command-a:prompt": 250,
+ "openrouter:cohere/command-a:completion": 1000,
+ "openrouter:openai/gpt-4o-mini-search-preview:prompt": 15,
+ "openrouter:openai/gpt-4o-mini-search-preview:completion": 60,
+ "openrouter:openai/gpt-4o-mini-search-preview:request": 2750000,
+ "openrouter:openai/gpt-4o-mini-search-preview:image": 21700,
+ "openrouter:openai/gpt-4o-search-preview:prompt": 250,
+ "openrouter:openai/gpt-4o-search-preview:completion": 1000,
+ "openrouter:openai/gpt-4o-search-preview:request": 3500000,
+ "openrouter:openai/gpt-4o-search-preview:image": 361300,
+ "openrouter:google/gemma-3-27b-it:prompt": 9,
+ "openrouter:google/gemma-3-27b-it:completion": 16,
+ "openrouter:google/gemma-3-27b-it:image": 2560,
+ "openrouter:thedrummer/skyfall-36b-v2:prompt": 8,
+ "openrouter:thedrummer/skyfall-36b-v2:completion": 33,
+ "openrouter:microsoft/phi-4-multimodal-instruct:prompt": 5,
+ "openrouter:microsoft/phi-4-multimodal-instruct:completion": 10,
+ "openrouter:microsoft/phi-4-multimodal-instruct:image": 17685,
+ "openrouter:perplexity/sonar-reasoning-pro:prompt": 200,
+ "openrouter:perplexity/sonar-reasoning-pro:completion": 800,
+ "openrouter:perplexity/sonar-reasoning-pro:web_search": 500000,
+ "openrouter:perplexity/sonar-pro:prompt": 300,
+ "openrouter:perplexity/sonar-pro:completion": 1500,
+ "openrouter:perplexity/sonar-pro:web_search": 500000,
+ "openrouter:perplexity/sonar-deep-research:prompt": 200,
+ "openrouter:perplexity/sonar-deep-research:completion": 800,
+ "openrouter:perplexity/sonar-deep-research:web_search": 500000,
+ "openrouter:perplexity/sonar-deep-research:internal_reasoning": 300,
+ "openrouter:qwen/qwq-32b:prompt": 15,
+ "openrouter:qwen/qwq-32b:completion": 40,
+ "openrouter:nousresearch/deephermes-3-llama-3-8b-preview:prompt": 3,
+ "openrouter:nousresearch/deephermes-3-llama-3-8b-preview:completion": 11,
+ "openrouter:google/gemini-2.0-flash-lite-001:prompt": 7,
+ "openrouter:google/gemini-2.0-flash-lite-001:completion": 30,
+ "openrouter:anthropic/claude-3.7-sonnet:prompt": 300,
+ "openrouter:anthropic/claude-3.7-sonnet:completion": 1500,
+ "openrouter:anthropic/claude-3.7-sonnet:image": 480000,
+ "openrouter:anthropic/claude-3.7-sonnet:input_cache_read": 30,
+ "openrouter:anthropic/claude-3.7-sonnet:input_cache_write": 375,
+ "openrouter:anthropic/claude-3.7-sonnet:thinking:prompt": 300,
+ "openrouter:anthropic/claude-3.7-sonnet:thinking:completion": 1500,
+ "openrouter:anthropic/claude-3.7-sonnet:thinking:image": 480000,
+ "openrouter:anthropic/claude-3.7-sonnet:thinking:input_cache_read": 30,
+ "openrouter:anthropic/claude-3.7-sonnet:thinking:input_cache_write": 375,
+ "openrouter:mistralai/mistral-saba:prompt": 20,
+ "openrouter:mistralai/mistral-saba:completion": 60,
+ "openrouter:cognitivecomputations/dolphin3.0-mistral-24b:prompt": 4,
+ "openrouter:cognitivecomputations/dolphin3.0-mistral-24b:completion": 17,
+ "openrouter:meta-llama/llama-guard-3-8b:prompt": 2,
+ "openrouter:meta-llama/llama-guard-3-8b:completion": 6,
+ "openrouter:openai/o3-mini-high:prompt": 110,
+ "openrouter:openai/o3-mini-high:completion": 440,
+ "openrouter:openai/o3-mini-high:input_cache_read": 55,
+ "openrouter:google/gemini-2.0-flash-001:prompt": 10,
+ "openrouter:google/gemini-2.0-flash-001:completion": 40,
+ "openrouter:google/gemini-2.0-flash-001:image": 2580,
+ "openrouter:google/gemini-2.0-flash-001:audio": 70,
+ "openrouter:google/gemini-2.0-flash-001:input_cache_read": 3,
+ "openrouter:google/gemini-2.0-flash-001:input_cache_write": 18,
+ "openrouter:qwen/qwen-vl-plus:prompt": 21,
+ "openrouter:qwen/qwen-vl-plus:completion": 63,
+ "openrouter:qwen/qwen-vl-plus:image": 26880,
+ "openrouter:aion-labs/aion-1.0:prompt": 400,
+ "openrouter:aion-labs/aion-1.0:completion": 800,
+ "openrouter:aion-labs/aion-1.0-mini:prompt": 70,
+ "openrouter:aion-labs/aion-1.0-mini:completion": 140,
+ "openrouter:aion-labs/aion-rp-llama-3.1-8b:prompt": 20,
+ "openrouter:aion-labs/aion-rp-llama-3.1-8b:completion": 20,
+ "openrouter:qwen/qwen-vl-max:prompt": 80,
+ "openrouter:qwen/qwen-vl-max:completion": 320,
+ "openrouter:qwen/qwen-vl-max:image": 102400,
+ "openrouter:qwen/qwen-turbo:prompt": 5,
+ "openrouter:qwen/qwen-turbo:completion": 20,
+ "openrouter:qwen/qwen-turbo:input_cache_read": 2,
+ "openrouter:qwen/qwen2.5-vl-72b-instruct:prompt": 8,
+ "openrouter:qwen/qwen2.5-vl-72b-instruct:completion": 33,
+ "openrouter:qwen/qwen-plus:prompt": 40,
+ "openrouter:qwen/qwen-plus:completion": 120,
+ "openrouter:qwen/qwen-plus:input_cache_read": 16,
+ "openrouter:qwen/qwen-max:prompt": 160,
+ "openrouter:qwen/qwen-max:completion": 640,
+ "openrouter:qwen/qwen-max:input_cache_read": 64,
+ "openrouter:openai/o3-mini:prompt": 110,
+ "openrouter:openai/o3-mini:completion": 440,
+ "openrouter:openai/o3-mini:input_cache_read": 55,
+ "openrouter:mistralai/mistral-small-24b-instruct-2501:prompt": 5,
+ "openrouter:mistralai/mistral-small-24b-instruct-2501:completion": 8,
+ "openrouter:deepseek/deepseek-r1-distill-qwen-32b:prompt": 27,
+ "openrouter:deepseek/deepseek-r1-distill-qwen-32b:completion": 27,
+ "openrouter:deepseek/deepseek-r1-distill-qwen-14b:prompt": 15,
+ "openrouter:deepseek/deepseek-r1-distill-qwen-14b:completion": 15,
+ "openrouter:perplexity/sonar-reasoning:prompt": 100,
+ "openrouter:perplexity/sonar-reasoning:completion": 500,
+ "openrouter:perplexity/sonar-reasoning:request": 500000,
+ "openrouter:perplexity/sonar:prompt": 100,
+ "openrouter:perplexity/sonar:completion": 100,
+ "openrouter:perplexity/sonar:request": 500000,
+ "openrouter:liquid/lfm-7b:prompt": 1,
+ "openrouter:liquid/lfm-7b:completion": 1,
+ "openrouter:liquid/lfm-3b:prompt": 2,
+ "openrouter:liquid/lfm-3b:completion": 2,
+ "openrouter:deepseek/deepseek-r1-distill-llama-70b:prompt": 3,
+ "openrouter:deepseek/deepseek-r1-distill-llama-70b:completion": 13,
+ "openrouter:deepseek/deepseek-r1:prompt": 40,
+ "openrouter:deepseek/deepseek-r1:completion": 200,
+ "openrouter:minimax/minimax-01:prompt": 20,
+ "openrouter:minimax/minimax-01:completion": 110,
+ "openrouter:mistralai/codestral-2501:prompt": 30,
+ "openrouter:mistralai/codestral-2501:completion": 90,
+ "openrouter:microsoft/phi-4:prompt": 6,
+ "openrouter:microsoft/phi-4:completion": 14,
+ "openrouter:sao10k/l3.1-70b-hanami-x1:prompt": 300,
+ "openrouter:sao10k/l3.1-70b-hanami-x1:completion": 300,
+ "openrouter:deepseek/deepseek-chat:prompt": 30,
+ "openrouter:deepseek/deepseek-chat:completion": 85,
+ "openrouter:sao10k/l3.3-euryale-70b:prompt": 65,
+ "openrouter:sao10k/l3.3-euryale-70b:completion": 75,
+ "openrouter:openai/o1:prompt": 1500,
+ "openrouter:openai/o1:completion": 6000,
+ "openrouter:openai/o1:image": 2167500,
+ "openrouter:openai/o1:input_cache_read": 750,
+ "openrouter:cohere/command-r7b-12-2024:prompt": 4,
+ "openrouter:cohere/command-r7b-12-2024:completion": 15,
+ "openrouter:meta-llama/llama-3.3-70b-instruct:prompt": 13,
+ "openrouter:meta-llama/llama-3.3-70b-instruct:completion": 39,
+ "openrouter:amazon/nova-lite-v1:prompt": 6,
+ "openrouter:amazon/nova-lite-v1:completion": 24,
+ "openrouter:amazon/nova-lite-v1:image": 9000,
+ "openrouter:amazon/nova-micro-v1:prompt": 4,
+ "openrouter:amazon/nova-micro-v1:completion": 14,
+ "openrouter:amazon/nova-pro-v1:prompt": 80,
+ "openrouter:amazon/nova-pro-v1:completion": 320,
+ "openrouter:amazon/nova-pro-v1:image": 120000,
+ "openrouter:openai/gpt-4o-2024-11-20:prompt": 250,
+ "openrouter:openai/gpt-4o-2024-11-20:completion": 1000,
+ "openrouter:openai/gpt-4o-2024-11-20:image": 361300,
+ "openrouter:openai/gpt-4o-2024-11-20:input_cache_read": 125,
+ "openrouter:mistralai/mistral-large-2411:prompt": 200,
+ "openrouter:mistralai/mistral-large-2411:completion": 600,
+ "openrouter:mistralai/mistral-large-2407:prompt": 200,
+ "openrouter:mistralai/mistral-large-2407:completion": 600,
+ "openrouter:mistralai/pixtral-large-2411:prompt": 200,
+ "openrouter:mistralai/pixtral-large-2411:completion": 600,
+ "openrouter:mistralai/pixtral-large-2411:image": 288800,
+ "openrouter:qwen/qwen-2.5-coder-32b-instruct:prompt": 4,
+ "openrouter:qwen/qwen-2.5-coder-32b-instruct:completion": 16,
+ "openrouter:raifle/sorcererlm-8x22b:prompt": 450,
+ "openrouter:raifle/sorcererlm-8x22b:completion": 450,
+ "openrouter:thedrummer/unslopnemo-12b:prompt": 40,
+ "openrouter:thedrummer/unslopnemo-12b:completion": 40,
+ "openrouter:anthropic/claude-3.5-haiku:prompt": 80,
+ "openrouter:anthropic/claude-3.5-haiku:completion": 400,
+ "openrouter:anthropic/claude-3.5-haiku:web_search": 1000000,
+ "openrouter:anthropic/claude-3.5-haiku:input_cache_read": 8,
+ "openrouter:anthropic/claude-3.5-haiku:input_cache_write": 100,
+ "openrouter:anthropic/claude-3.5-haiku-20241022:prompt": 80,
+ "openrouter:anthropic/claude-3.5-haiku-20241022:completion": 400,
+ "openrouter:anthropic/claude-3.5-haiku-20241022:input_cache_read": 8,
+ "openrouter:anthropic/claude-3.5-haiku-20241022:input_cache_write": 100,
+ "openrouter:anthracite-org/magnum-v4-72b:prompt": 250,
+ "openrouter:anthracite-org/magnum-v4-72b:completion": 500,
+ "openrouter:anthropic/claude-3.5-sonnet:prompt": 300,
+ "openrouter:anthropic/claude-3.5-sonnet:completion": 1500,
+ "openrouter:anthropic/claude-3.5-sonnet:image": 480000,
+ "openrouter:anthropic/claude-3.5-sonnet:input_cache_read": 30,
+ "openrouter:anthropic/claude-3.5-sonnet:input_cache_write": 375,
+ "openrouter:mistralai/ministral-3b:prompt": 4,
+ "openrouter:mistralai/ministral-3b:completion": 4,
+ "openrouter:mistralai/ministral-8b:prompt": 10,
+ "openrouter:mistralai/ministral-8b:completion": 10,
+ "openrouter:qwen/qwen-2.5-7b-instruct:prompt": 4,
+ "openrouter:qwen/qwen-2.5-7b-instruct:completion": 10,
+ "openrouter:nvidia/llama-3.1-nemotron-70b-instruct:prompt": 60,
+ "openrouter:nvidia/llama-3.1-nemotron-70b-instruct:completion": 60,
+ "openrouter:inflection/inflection-3-productivity:prompt": 250,
+ "openrouter:inflection/inflection-3-productivity:completion": 1000,
+ "openrouter:inflection/inflection-3-pi:prompt": 250,
+ "openrouter:inflection/inflection-3-pi:completion": 1000,
+ "openrouter:thedrummer/rocinante-12b:prompt": 17,
+ "openrouter:thedrummer/rocinante-12b:completion": 43,
+ "openrouter:anthracite-org/magnum-v2-72b:prompt": 300,
+ "openrouter:anthracite-org/magnum-v2-72b:completion": 300,
+ "openrouter:meta-llama/llama-3.2-3b-instruct:prompt": 2,
+ "openrouter:meta-llama/llama-3.2-3b-instruct:completion": 2,
+ "openrouter:meta-llama/llama-3.2-1b-instruct:prompt": 1,
+ "openrouter:meta-llama/llama-3.2-1b-instruct:completion": 1,
+ "openrouter:meta-llama/llama-3.2-11b-vision-instruct:prompt": 5,
+ "openrouter:meta-llama/llama-3.2-11b-vision-instruct:completion": 5,
+ "openrouter:meta-llama/llama-3.2-11b-vision-instruct:image": 7948,
+ "openrouter:meta-llama/llama-3.2-90b-vision-instruct:prompt": 35,
+ "openrouter:meta-llama/llama-3.2-90b-vision-instruct:completion": 40,
+ "openrouter:meta-llama/llama-3.2-90b-vision-instruct:image": 50580,
+ "openrouter:qwen/qwen-2.5-72b-instruct:prompt": 7,
+ "openrouter:qwen/qwen-2.5-72b-instruct:completion": 26,
+ "openrouter:neversleep/llama-3.1-lumimaid-8b:prompt": 9,
+ "openrouter:neversleep/llama-3.1-lumimaid-8b:completion": 60,
+ "openrouter:openai/o1-mini-2024-09-12:prompt": 110,
+ "openrouter:openai/o1-mini-2024-09-12:completion": 440,
+ "openrouter:openai/o1-mini-2024-09-12:input_cache_read": 55,
+ "openrouter:openai/o1-mini:prompt": 110,
+ "openrouter:openai/o1-mini:completion": 440,
+ "openrouter:openai/o1-mini:input_cache_read": 55,
+ "openrouter:mistralai/pixtral-12b:prompt": 10,
+ "openrouter:mistralai/pixtral-12b:completion": 10,
+ "openrouter:mistralai/pixtral-12b:image": 14450,
+ "openrouter:cohere/command-r-08-2024:prompt": 15,
+ "openrouter:cohere/command-r-08-2024:completion": 60,
+ "openrouter:cohere/command-r-plus-08-2024:prompt": 250,
+ "openrouter:cohere/command-r-plus-08-2024:completion": 1000,
+ "openrouter:sao10k/l3.1-euryale-70b:prompt": 65,
+ "openrouter:sao10k/l3.1-euryale-70b:completion": 75,
+ "openrouter:qwen/qwen-2.5-vl-7b-instruct:prompt": 20,
+ "openrouter:qwen/qwen-2.5-vl-7b-instruct:completion": 20,
+ "openrouter:qwen/qwen-2.5-vl-7b-instruct:image": 14450,
+ "openrouter:microsoft/phi-3.5-mini-128k-instruct:prompt": 10,
+ "openrouter:microsoft/phi-3.5-mini-128k-instruct:completion": 10,
+ "openrouter:nousresearch/hermes-3-llama-3.1-70b:prompt": 30,
+ "openrouter:nousresearch/hermes-3-llama-3.1-70b:completion": 30,
+ "openrouter:nousresearch/hermes-3-llama-3.1-405b:prompt": 100,
+ "openrouter:nousresearch/hermes-3-llama-3.1-405b:completion": 100,
+ "openrouter:openai/chatgpt-4o-latest:prompt": 500,
+ "openrouter:openai/chatgpt-4o-latest:completion": 1500,
+ "openrouter:openai/chatgpt-4o-latest:image": 722500,
+ "openrouter:sao10k/l3-lunaris-8b:prompt": 4,
+ "openrouter:sao10k/l3-lunaris-8b:completion": 5,
+ "openrouter:openai/gpt-4o-2024-08-06:prompt": 250,
+ "openrouter:openai/gpt-4o-2024-08-06:completion": 1000,
+ "openrouter:openai/gpt-4o-2024-08-06:image": 361300,
+ "openrouter:openai/gpt-4o-2024-08-06:input_cache_read": 125,
+ "openrouter:meta-llama/llama-3.1-405b:prompt": 400,
+ "openrouter:meta-llama/llama-3.1-405b:completion": 400,
+ "openrouter:meta-llama/llama-3.1-8b-instruct:prompt": 2,
+ "openrouter:meta-llama/llama-3.1-8b-instruct:completion": 3,
+ "openrouter:meta-llama/llama-3.1-70b-instruct:prompt": 40,
+ "openrouter:meta-llama/llama-3.1-70b-instruct:completion": 40,
+ "openrouter:meta-llama/llama-3.1-405b-instruct:prompt": 80,
+ "openrouter:meta-llama/llama-3.1-405b-instruct:completion": 80,
+ "openrouter:mistralai/mistral-nemo:prompt": 2,
+ "openrouter:mistralai/mistral-nemo:completion": 4,
+ "openrouter:openai/gpt-4o-mini-2024-07-18:prompt": 15,
+ "openrouter:openai/gpt-4o-mini-2024-07-18:completion": 60,
+ "openrouter:openai/gpt-4o-mini-2024-07-18:image": 722500,
+ "openrouter:openai/gpt-4o-mini-2024-07-18:input_cache_read": 7,
+ "openrouter:openai/gpt-4o-mini:prompt": 15,
+ "openrouter:openai/gpt-4o-mini:completion": 60,
+ "openrouter:openai/gpt-4o-mini:image": 21700,
+ "openrouter:openai/gpt-4o-mini:input_cache_read": 7,
+ "openrouter:google/gemma-2-27b-it:prompt": 65,
+ "openrouter:google/gemma-2-27b-it:completion": 65,
+ "openrouter:google/gemma-2-9b-it:prompt": 1,
+ "openrouter:google/gemma-2-9b-it:completion": 3,
+ "openrouter:anthropic/claude-3.5-sonnet-20240620:prompt": 300,
+ "openrouter:anthropic/claude-3.5-sonnet-20240620:completion": 1500,
+ "openrouter:anthropic/claude-3.5-sonnet-20240620:image": 480000,
+ "openrouter:anthropic/claude-3.5-sonnet-20240620:input_cache_read": 30,
+ "openrouter:anthropic/claude-3.5-sonnet-20240620:input_cache_write": 375,
+ "openrouter:sao10k/l3-euryale-70b:prompt": 148,
+ "openrouter:sao10k/l3-euryale-70b:completion": 148,
+ "openrouter:mistralai/mistral-7b-instruct-v0.3:prompt": 3,
+ "openrouter:mistralai/mistral-7b-instruct-v0.3:completion": 5,
+ "openrouter:mistralai/mistral-7b-instruct:prompt": 3,
+ "openrouter:mistralai/mistral-7b-instruct:completion": 5,
+ "openrouter:nousresearch/hermes-2-pro-llama-3-8b:prompt": 3,
+ "openrouter:nousresearch/hermes-2-pro-llama-3-8b:completion": 8,
+ "openrouter:microsoft/phi-3-mini-128k-instruct:prompt": 10,
+ "openrouter:microsoft/phi-3-mini-128k-instruct:completion": 10,
+ "openrouter:microsoft/phi-3-medium-128k-instruct:prompt": 100,
+ "openrouter:microsoft/phi-3-medium-128k-instruct:completion": 100,
+ "openrouter:openai/gpt-4o-2024-05-13:prompt": 500,
+ "openrouter:openai/gpt-4o-2024-05-13:completion": 1500,
+ "openrouter:openai/gpt-4o-2024-05-13:image": 722500,
+ "openrouter:openai/gpt-4o:prompt": 250,
+ "openrouter:openai/gpt-4o:completion": 1000,
+ "openrouter:openai/gpt-4o:image": 361300,
+ "openrouter:openai/gpt-4o:input_cache_read": 125,
+ "openrouter:openai/gpt-4o:extended:prompt": 600,
+ "openrouter:openai/gpt-4o:extended:completion": 1800,
+ "openrouter:openai/gpt-4o:extended:image": 722500,
+ "openrouter:meta-llama/llama-guard-2-8b:prompt": 20,
+ "openrouter:meta-llama/llama-guard-2-8b:completion": 20,
+ "openrouter:meta-llama/llama-3-8b-instruct:prompt": 3,
+ "openrouter:meta-llama/llama-3-8b-instruct:completion": 6,
+ "openrouter:meta-llama/llama-3-70b-instruct:prompt": 30,
+ "openrouter:meta-llama/llama-3-70b-instruct:completion": 40,
+ "openrouter:mistralai/mixtral-8x22b-instruct:prompt": 90,
+ "openrouter:mistralai/mixtral-8x22b-instruct:completion": 90,
+ "openrouter:microsoft/wizardlm-2-8x22b:prompt": 48,
+ "openrouter:microsoft/wizardlm-2-8x22b:completion": 48,
+ "openrouter:openai/gpt-4-turbo:prompt": 1000,
+ "openrouter:openai/gpt-4-turbo:completion": 3000,
+ "openrouter:openai/gpt-4-turbo:image": 1445000,
+ "openrouter:anthropic/claude-3-haiku:prompt": 25,
+ "openrouter:anthropic/claude-3-haiku:completion": 125,
+ "openrouter:anthropic/claude-3-haiku:image": 40000,
+ "openrouter:anthropic/claude-3-haiku:input_cache_read": 3,
+ "openrouter:anthropic/claude-3-haiku:input_cache_write": 30,
+ "openrouter:anthropic/claude-3-opus:prompt": 1500,
+ "openrouter:anthropic/claude-3-opus:completion": 7500,
+ "openrouter:anthropic/claude-3-opus:image": 2400000,
+ "openrouter:anthropic/claude-3-opus:input_cache_read": 150,
+ "openrouter:anthropic/claude-3-opus:input_cache_write": 1875,
+ "openrouter:mistralai/mistral-large:prompt": 200,
+ "openrouter:mistralai/mistral-large:completion": 600,
+ "openrouter:openai/gpt-4-turbo-preview:prompt": 1000,
+ "openrouter:openai/gpt-4-turbo-preview:completion": 3000,
+ "openrouter:openai/gpt-3.5-turbo-0613:prompt": 100,
+ "openrouter:openai/gpt-3.5-turbo-0613:completion": 200,
+ "openrouter:mistralai/mistral-small:prompt": 20,
+ "openrouter:mistralai/mistral-small:completion": 60,
+ "openrouter:mistralai/mistral-tiny:prompt": 25,
+ "openrouter:mistralai/mistral-tiny:completion": 25,
+ "openrouter:mistralai/mistral-7b-instruct-v0.2:prompt": 20,
+ "openrouter:mistralai/mistral-7b-instruct-v0.2:completion": 20,
+ "openrouter:mistralai/mixtral-8x7b-instruct:prompt": 54,
+ "openrouter:mistralai/mixtral-8x7b-instruct:completion": 54,
+ "openrouter:neversleep/noromaid-20b:prompt": 100,
+ "openrouter:neversleep/noromaid-20b:completion": 175,
+ "openrouter:alpindale/goliath-120b:prompt": 400,
+ "openrouter:alpindale/goliath-120b:completion": 550,
+ "openrouter:openrouter/auto:prompt": -100000000,
+ "openrouter:openrouter/auto:completion": -100000000,
+ "openrouter:openai/gpt-4-1106-preview:prompt": 1000,
+ "openrouter:openai/gpt-4-1106-preview:completion": 3000,
+ "openrouter:mistralai/mistral-7b-instruct-v0.1:prompt": 11,
+ "openrouter:mistralai/mistral-7b-instruct-v0.1:completion": 19,
+ "openrouter:openai/gpt-3.5-turbo-instruct:prompt": 150,
+ "openrouter:openai/gpt-3.5-turbo-instruct:completion": 200,
+ "openrouter:openai/gpt-3.5-turbo-16k:prompt": 300,
+ "openrouter:openai/gpt-3.5-turbo-16k:completion": 400,
+ "openrouter:mancer/weaver:prompt": 113,
+ "openrouter:mancer/weaver:completion": 113,
+ "openrouter:undi95/remm-slerp-l2-13b:prompt": 45,
+ "openrouter:undi95/remm-slerp-l2-13b:completion": 65,
+ "openrouter:gryphe/mythomax-l2-13b:prompt": 5,
+ "openrouter:gryphe/mythomax-l2-13b:completion": 9,
+ "openrouter:openai/gpt-4:prompt": 3000,
+ "openrouter:openai/gpt-4:completion": 6000,
+ "openrouter:openai/gpt-3.5-turbo:prompt": 50,
+ "openrouter:openai/gpt-3.5-turbo:completion": 150,
+ "openrouter:openai/gpt-4-0314:prompt": 3000,
+ "openrouter:openai/gpt-4-0314:completion": 6000,
};
diff --git a/src/backend/src/services/MeteringService/costMaps/togetherCostMap.ts b/src/backend/src/services/MeteringService/costMaps/togetherCostMap.ts
index a6a9bf6b..922fbe70 100644
--- a/src/backend/src/services/MeteringService/costMaps/togetherCostMap.ts
+++ b/src/backend/src/services/MeteringService/costMaps/togetherCostMap.ts
@@ -7,6 +7,6 @@
export const TOGETHER_COST_MAP = {
// Test model (hardcoded)
- 'together:model-fallback-test-1:input': 10,
- 'together:model-fallback-test-1:output': 10,
+ "together:model-fallback-test-1:input": 10,
+ "together:model-fallback-test-1:output": 10,
};
\ No newline at end of file
diff --git a/src/backend/src/services/MeteringService/costMaps/xaiCostMap.ts b/src/backend/src/services/MeteringService/costMaps/xaiCostMap.ts
index 2653ef54..0df948cb 100644
--- a/src/backend/src/services/MeteringService/costMaps/xaiCostMap.ts
+++ b/src/backend/src/services/MeteringService/costMaps/xaiCostMap.ts
@@ -19,35 +19,35 @@
export const XAI_COST_MAP = {
// Grok Beta
- 'xai:grok-beta:prompt_tokens': 500,
- 'xai:grok-beta:completion-tokens': 1500,
+ "xai:grok-beta:prompt_tokens": 500,
+ "xai:grok-beta:completion-tokens": 1500,
// Grok Vision Beta
- 'xai:grok-vision-beta:prompt_tokens': 500,
- 'xai:grok-vision-beta:completion-tokens': 1500,
- 'xai:grok-vision-beta:image': 1000,
+ "xai:grok-vision-beta:prompt_tokens": 500,
+ "xai:grok-vision-beta:completion-tokens": 1500,
+ "xai:grok-vision-beta:image": 1000,
// Grok 3
- 'xai:grok-3:prompt_tokens': 300,
- 'xai:grok-3:completion-tokens': 1500,
+ "xai:grok-3:prompt_tokens": 300,
+ "xai:grok-3:completion-tokens": 1500,
// Grok 3 Fast
- 'xai:grok-3-fast:prompt_tokens': 500,
- 'xai:grok-3-fast:completion-tokens': 2500,
+ "xai:grok-3-fast:prompt_tokens": 500,
+ "xai:grok-3-fast:completion-tokens": 2500,
// Grok 3 Mini
- 'xai:grok-3-mini:prompt_tokens': 30,
- 'xai:grok-3-mini:completion-tokens': 50,
+ "xai:grok-3-mini:prompt_tokens": 30,
+ "xai:grok-3-mini:completion-tokens": 50,
// Grok 3 Mini Fast
- 'xai:grok-3-mini-fast:prompt_tokens': 60,
- 'xai:grok-3-mini-fast:completion-tokens': 400,
+ "xai:grok-3-mini-fast:prompt_tokens": 60,
+ "xai:grok-3-mini-fast:completion-tokens": 400,
// Grok 2 Vision
- 'xai:grok-2-vision:prompt_tokens': 200,
- 'xai:grok-2-vision:completion-tokens': 1000,
+ "xai:grok-2-vision:prompt_tokens": 200,
+ "xai:grok-2-vision:completion-tokens": 1000,
// Grok 2
- 'xai:grok-2:prompt_tokens': 200,
- 'xai:grok-2:completion-tokens': 1000,
+ "xai:grok-2:prompt_tokens": 200,
+ "xai:grok-2:completion-tokens": 1000,
};
\ No newline at end of file
diff --git a/src/backend/src/services/MeteringService/subPolicies/registeredUserFreePolicy.ts b/src/backend/src/services/MeteringService/subPolicies/registeredUserFreePolicy.ts
index 1eae5e60..fc8fa7d0 100644
--- a/src/backend/src/services/MeteringService/subPolicies/registeredUserFreePolicy.ts
+++ b/src/backend/src/services/MeteringService/subPolicies/registeredUserFreePolicy.ts
@@ -1,4 +1,4 @@
-import { toMicroCents } from '../utils';
+import { toMicroCents } from "../utils";
export const REGISTERED_USER_FREE = {
id: 'user_free',
diff --git a/src/backend/src/services/MeteringService/subPolicies/tempUserFreePolicy.ts b/src/backend/src/services/MeteringService/subPolicies/tempUserFreePolicy.ts
index 47eb9e0b..c7a8fadc 100644
--- a/src/backend/src/services/MeteringService/subPolicies/tempUserFreePolicy.ts
+++ b/src/backend/src/services/MeteringService/subPolicies/tempUserFreePolicy.ts
@@ -1,4 +1,4 @@
-import { toMicroCents } from '../utils';
+import { toMicroCents } from "../utils";
export const TEMP_USER_FREE = {
id: 'temp_free',
diff --git a/src/backend/src/services/MeteringService/utils.ts b/src/backend/src/services/MeteringService/utils.ts
index 3a8dc208..938a3380 100644
--- a/src/backend/src/services/MeteringService/utils.ts
+++ b/src/backend/src/services/MeteringService/utils.ts
@@ -1 +1 @@
-export const toMicroCents = (dollars: number) => dollars * 1_000_000 * 100;
+export const toMicroCents = (dollars: number) => Math.round(dollars * 1_000_000 * 100);
diff --git a/src/backend/src/services/OperationTraceService.js b/src/backend/src/services/OperationTraceService.js
index afde3eb7..91f380be 100644
--- a/src/backend/src/services/OperationTraceService.js
+++ b/src/backend/src/services/OperationTraceService.js
@@ -28,13 +28,14 @@ const { AssignableMethodsFeature } = require("../traits/AssignableMethodsFeature
// and is utilized throughout the OperationTraceService to manage frames.
const CONTEXT_KEY = Context.make_context_key('operation-trace');
+
/**
* @class OperationFrame
* @description The `OperationFrame` class represents a frame within an operation trace. It is designed to manage the state, attributes, and hierarchy of frames within an operational context. This class provides methods to set status, calculate effective status, add tags, attributes, messages, errors, children, and describe the frame. It also includes methods to recursively search through frames to find attributes and handle frame completion.
*/
class OperationFrame {
static LOG_DEBUG = true;
- constructor({ parent, label, x }) {
+ constructor ({ parent, label, x }) {
this.parent = parent;
this.label = label;
this.tags = [];
@@ -47,28 +48,31 @@ class OperationFrame {
this.id = require('uuid').v4();
this.log = (x ?? Context).get('services').get('log-service').create(
- `frame:${this.id}`,
- { concern: 'filesystem' });
+ `frame:${this.id}`,
+ { concern: 'filesystem' },
+ );
}
static FRAME_STATUS_PENDING = { label: 'pending' };
- static FRAME_STATUS_WORKING = { label: 'working' };
+ static FRAME_STATUS_WORKING = { label: 'working', };
static FRAME_STATUS_STUCK = { label: 'stuck' };
static FRAME_STATUS_READY = { label: 'ready' };
static FRAME_STATUS_DONE = { label: 'done' };
- set status(status) {
+ set status (status) {
this.status_ = status;
this._calc_effective_status();
- this.log.debug(`FRAME STATUS ${status.label} ` +
+ this.log.debug(
+ `FRAME STATUS ${status.label} ` +
(status !== this.effective_status_
? `(effective: ${this.effective_status_.label}) `
: ''),
- {
- tags: this.tags,
- ...this.attributes,
- });
+ {
+ tags: this.tags,
+ ...this.attributes,
+ }
+ );
if ( this.parent ) {
this.parent._calc_effective_status();
@@ -80,7 +84,7 @@ class OperationFrame {
*
* @param {Object} status - The new status to set.
*/
- _calc_effective_status() {
+ _calc_effective_status () {
for ( const child of this.children ) {
if ( child.status === OperationFrame.FRAME_STATUS_STUCK ) {
this.effective_status_ = OperationFrame.FRAME_STATUS_STUCK;
@@ -109,6 +113,7 @@ class OperationFrame {
}
}
+
/**
* Gets the effective status of the operation frame.
*
@@ -119,47 +124,48 @@ class OperationFrame {
*
* @return {Object} The effective status of the operation frame.
*/
- get status() {
+ get status () {
return this.effective_status_;
}
- tag(...tags) {
+ tag (...tags) {
this.tags.push(...tags);
return this;
}
- attr(key, value) {
+ attr (key, value) {
this.attributes[key] = value;
return this;
}
// recursively go through frames to find the attribute
- get_attr(key) {
+ get_attr (key) {
if ( this.attributes[key] ) return this.attributes[key];
if ( this.parent ) return this.parent.get_attr(key);
}
- log(message) {
+ log (message) {
this.messages.push(message);
return this;
}
- error(err) {
+ error (err) {
this.error_ = err;
return this;
}
- push_child(frame) {
+ push_child (frame) {
this.children.push(frame);
return this;
}
+
/**
* Recursively traverses the frame hierarchy to find the root frame.
*
* @returns {OperationFrame} The root frame of the current frame hierarchy.
*/
- get_root_frame() {
+ get_root_frame () {
let frame = this;
while ( frame.parent ) {
frame = frame.parent;
@@ -167,17 +173,18 @@ class OperationFrame {
return frame;
}
+
/**
* Marks the operation frame as done.
* This method sets the status of the operation frame to 'done' and updates
* the effective status accordingly. It triggers a recalculation of the
* effective status for parent frames if necessary.
*/
- done() {
+ done () {
this.status = OperationFrame.FRAME_STATUS_DONE;
}
- describe(show_tree, highlight_frame) {
+ describe (show_tree, highlight_frame) {
let s = this.label + ` (${this.children.length})`;
if ( this.tags.length ) {
s += ' ' + this.tags.join(' ');
@@ -194,6 +201,7 @@ class OperationFrame {
const prefix_deep = '│ ';
const prefix_deep_end = ' ';
+
/**
* Recursively builds a string representation of the frame and its children.
*
@@ -211,13 +219,14 @@ class OperationFrame {
if ( child === highlight_frame ) s += `\x1B[0m`;
recurse(child, prefix + (is_last ? prefix_deep_end : prefix_deep));
}
- };
+ }
if ( show_tree ) recurse(this, '');
return s;
}
}
+
/**
* @class OperationTraceService
* @classdesc The OperationTraceService class manages operation frames and their statuses.
@@ -227,7 +236,7 @@ class OperationFrame {
class OperationTraceService {
static CONCERN = 'filesystem';
- constructor({ services }) {
+ constructor ({ services }) {
this.log = services.get('log-service').create('operation-trace', {
concern: this.constructor.CONCERN,
});
@@ -236,6 +245,7 @@ class OperationTraceService {
this.ongoing = {};
}
+
/**
* Adds a new operation frame to the trace.
*
@@ -248,20 +258,22 @@ class OperationTraceService {
* @param {?Object} [x] - The context for the operation frame.
* @returns {OperationFrame} The new operation frame.
*/
- async add_frame(label) {
+ async add_frame (label) {
return this.add_frame_sync(label);
}
- add_frame_sync(label, x) {
+ add_frame_sync (label, x) {
if ( x ) {
- this.log.debug('add_frame_sync() called with explicit context: ' +
- x.describe());
+ this.log.debug(
+ 'add_frame_sync() called with explicit context: ' +
+ x.describe()
+ );
}
let parent = (x ?? Context).get(this.ckey('frame'));
const frame = new OperationFrame({
parent: parent || null,
label,
- x,
+ x
});
parent && parent.push_child(frame);
this.log.debug(`FRAME START ` + frame.describe());
@@ -274,11 +286,12 @@ class OperationTraceService {
return frame;
}
- ckey(key) {
+ ckey (key) {
return CONTEXT_KEY + ':' + key;
}
}
+
/**
* @class BaseOperation
* @extends AdvancedBase
@@ -293,7 +306,8 @@ class BaseOperation extends AdvancedBase {
new ContextAwareFeature(),
new OtelFeature(['run']),
new AssignableMethodsFeature(),
- ];
+ ]
+
/**
* Executes the operation with the provided values.
@@ -302,23 +316,23 @@ class BaseOperation extends AdvancedBase {
* executes the `_run` method, and handles post-run logic. It also manages the status of child frames
* and handles errors, updating the frame's attributes accordingly.
*
- * @param {Object} firstArg - The values to be used in the operation. TODO DS: support multiple args with old state assignment?
- * @param {...unknown} rest - rest of args passed in only to children
+ * @param {Object} values - The values to be used in the operation.
* @returns {Promise<*>} - The result of the operation.
* @throws {Error} - If the frame is missing or any other error occurs during the operation.
*/
- async run(firstArg, ...rest) {
- this.values = firstArg;
+ async run (values) {
+ this.values = values;
- firstArg.user = firstArg.user ??
- (firstArg.actor ? firstArg.actor.type.user : undefined);
+ values.user = values.user ??
+ (values.actor ? values.actor.type.user : undefined);
// getting context with a new operation frame
- let x, frame;
- x = Context.get();
- const operationTraceSvc = x.get('services').get('operationTrace');
- frame = await operationTraceSvc.add_frame(this.constructor.name);
- x = x.sub({ [operationTraceSvc.ckey('frame')]: frame });
+ let x, frame; {
+ x = Context.get();
+ const operationTraceSvc = x.get('services').get('operationTrace');
+ frame = await operationTraceSvc.add_frame(this.constructor.name);
+ x = x.sub({ [operationTraceSvc.ckey('frame')]: frame });
+ }
// the frame will be an explicit property as well as being in context
// (for convenience)
@@ -326,12 +340,12 @@ class BaseOperation extends AdvancedBase {
// let's make the logger for it too
this.log = x.get('services').get('log-service').create(
- this.constructor.name, {
- operation: frame.id,
- ...(this.constructor.CONCERN ? {
- concern: this.constructor.CONCERN,
- } : {}),
- });
+ this.constructor.name, {
+ operation: frame.id,
+ ...(this.constructor.CONCERN ? {
+ concern: this.constructor.CONCERN,
+ } : {})
+ });
// Run operation in new context
try {
@@ -345,7 +359,7 @@ class BaseOperation extends AdvancedBase {
}
frame.status = OperationFrame.FRAME_STATUS_WORKING;
this.checkpoint('._run()');
- const res = await this._run(firstArg, ...rest); // TODO DS: simplify this, why are the passed in values being stored in class state?
+ const res = await this._run();
this.checkpoint('._post_run()');
const { any_async } = this._post_run();
this.checkpoint('delegate .run_() returned');
@@ -364,22 +378,23 @@ class BaseOperation extends AdvancedBase {
}
}
- checkpoint(name) {
+ checkpoint (name) {
this.frame.checkpoint = name;
}
- field(key, value) {
+ field (key, value) {
this.frame.attributes[key] = value;
}
+
/**
* Actions to perform after running.
- *
+ *
* If child operation frames think they're still pending, mark them as stuck;
* all child frames at least reach working state before the parent operation
- * completes.
+ * completes.
*/
- _post_run() {
+ _post_run () {
let any_async = false;
for ( const child of this.frame.children ) {
if ( child.status === OperationFrame.FRAME_STATUS_PENDING ) {
diff --git a/src/backend/src/services/repositories/DBKVStore/DBKVStore.ts b/src/backend/src/services/repositories/DBKVStore/DBKVStore.ts
index fdda5f0a..c04908e6 100644
--- a/src/backend/src/services/repositories/DBKVStore/DBKVStore.ts
+++ b/src/backend/src/services/repositories/DBKVStore/DBKVStore.ts
@@ -1,11 +1,11 @@
-import murmurhash from 'murmurhash';
+import murmurhash from "murmurhash";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import APIError from '../../../api/APIError.js';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
-import { Context } from '../../../util/context.js';
-import type { MeteringAndBillingService } from '../../MeteringService/MeteringService.js';
+import { Context } from "../../../util/context.js";
+import type { MeteringAndBillingService } from "../../MeteringService/MeteringService.js";
const GLOBAL_APP_KEY = 'global';
@@ -258,16 +258,13 @@ export class DBKVStore {
return await this.#expireat(key, timestamp);
}
- async incr>({ key, pathAndAmountMap }: { key: string, pathAndAmountMap: T }): Promise> {
- if ( Object.values(pathAndAmountMap).find((v) => typeof v !== 'number') ) {
- throw new Error('All values in pathAndAmountMap must be numbers');
- }
+ async incr>({ key, pathAndAmountMap }: { key: string, pathAndAmountMap: T }): Promise> {
let currVal = await this.get({ key });
const pathEntries = Object.entries(pathAndAmountMap);
if ( typeof currVal !== 'object' && pathEntries.length <= 1 && !pathEntries[0]?.[0] ) {
const amount = pathEntries[0]?.[1] ?? 1;
this.set({ key, value: (Number(currVal) || 0) + amount });
- return ((Number(currVal) || 0) + amount) as T extends { '': number } ? number : Record;
+ return ((Number(currVal) || 0) + amount) as T extends { "": number } ? number : Record;
}
// TODO DS: support arrays this also needs dynamodb implementation
if ( Array.isArray(currVal) ) {
@@ -305,7 +302,7 @@ export class DBKVStore {
obj[lastPart] += amount;
}
this.set({ key, value: currVal });
- return currVal as T extends { '': number } ? number : Record;
+ return currVal as T extends { "": number } ? number : Record;
}
async decr(...params: Parameters): ReturnType {