diff --git a/src/daily-cache.ts b/src/daily-cache.ts index b162b9a2..0fa47095 100644 --- a/src/daily-cache.ts +++ b/src/daily-cache.ts @@ -308,7 +308,13 @@ import type { DateRange, ProjectSummary } from './types.js' // billed again at the cache rates. Only input tokens and estimated cost fall; // call counts are unchanged, so no PENDING_REDERIVE_PROVIDER_VERSIONS entry is // needed. -export const DAILY_CACHE_VERSION = 63 +// v64: Kimi Code's `kimi-for-coding` prices by the model it served on the call's +// date (K2.5, K2.6, K2.7 Code) instead of retired K2 Thinking, and +// `kimi-for-coding-highspeed` prices at K2.7 Code HighSpeed instead of $0. +// From 11 Sep 2026 the alias served K2.8 Preview, which has no published rate; +// those calls price as K2.7 Code and are marked estimated. +// Calls are unchanged, so no PENDING_REDERIVE_PROVIDER_VERSIONS entry is needed. +export const DAILY_CACHE_VERSION = 64 const MIN_SUPPORTED_VERSION = 28 /// Providers whose per-day CALL COUNT means something different at diff --git a/src/models.ts b/src/models.ts index eefac44b..31c646b8 100644 --- a/src/models.ts +++ b/src/models.ts @@ -131,6 +131,9 @@ const BUILTIN_PRICE_OVERRIDES: Record = { 'composer-2': [0.5e-6, 2.5e-6, 0.5e-6, 0.2e-6], 'composer-1.5': [3.5e-6, 17.5e-6, 3.5e-6, 0.35e-6], 'composer-1': [1.25e-6, 10e-6, 1.25e-6, 0.125e-6], + // Moonshot's published rate (platform.kimi.ai/docs/pricing/chat): $1.90 miss, + // $0.38 hit, $8.00 output. LiteLLM only carries a reseller row for it. + 'kimi-k2.7-code-highspeed': [1.9e-6, 8e-6, null, 0.38e-6], } // Assemble a ModelCosts, applying the cache-cost heuristics (write = 1.25x @@ -676,8 +679,14 @@ const BUILTIN_ALIASES: Record = { 'orcarouter/fusion-flash': 'openai/gpt-oss-120b', 'orcarouter/fusion-mini': 'openai/gpt-oss-120b', 'kimi-auto': 'kimi-k2-thinking', - 'kimi-code': 'kimi-k2-thinking', - 'kimi-for-coding': 'kimi-k2-thinking', + // `kimi-for-coding` is Kimi Code's moving alias; `kimi-code` is kimi-cli's + // spelling of the same SKU. pricingModelAt prices older calls by the model + // the alias served then. K2.8 Preview (11 Sep 2026 on) has no Open Platform + // price, so it stays on K2.7 Code's. + 'kimi-code': 'kimi-k2.7-code', + 'kimi-for-coding': 'kimi-k2.7-code', + // HighSpeed has been K2.7 Code HighSpeed since it launched on 9 Jul 2026. + 'kimi-for-coding-highspeed': 'kimi-k2.7-code-highspeed', // Kimi Code wires report the bare `k3` id in llm.request.model; without an // alias those calls priced at $0 and the provider looked absent in the UI. 'k3': 'kimi-k3', @@ -989,7 +998,6 @@ export function isBuiltInFlatRateModel(model: string): boolean { if ( leaf === 'auto' || leaf === 'auto-genius' - || leaf === 'kimi-for-coding-highspeed' ) return true if (leaf.startsWith('grok-composer-')) return true if (leaf.startsWith('warp-auto-')) return true @@ -1244,13 +1252,39 @@ function stripKnownPricingVariantSuffix(model: string): string | null { } const AUTO_REVIEW_LUNA_FROM = Date.parse('2026-07-30T00:00:00Z') +// kimi-cli labelled the alias "powered by kimi-k2.5" from 27 Jan 2026 (1.2) and +// dropped that on 13 Apr 2026 (#1860) as K2.6 rolled out; Kimi Code's What's +// New dates K2.7 Code to 12 Jun 2026. What it served before K2.5 is unsourced, +// so those calls keep the K2 Thinking rate they always had. +const KIMI_CODING_K2_5_FROM = Date.parse('2026-01-27T00:00:00Z') +const KIMI_CODING_K2_6_FROM = Date.parse('2026-04-13T00:00:00Z') +const KIMI_CODING_K2_7_FROM = Date.parse('2026-06-12T00:00:00Z') +const KIMI_CODING_K2_8_FROM = Date.parse('2026-09-11T00:00:00Z') -/// The model a call is priced by. Only `codex-auto-review` depends on the -/// call's date (see BUILTIN_ALIASES); a user alias for it still wins, and a -/// missing or unparseable timestamp keeps the forward default. +/// The model a call is priced by. Only `codex-auto-review` and the Kimi Code +/// alias depend on the call's date (see BUILTIN_ALIASES); a user alias for +/// them still wins, and a missing or unparseable timestamp keeps the forward +/// default. export function pricingModelAt(model: string, timestamp: string | undefined): string { - if (model.toLowerCase() !== 'codex-auto-review' || Object.hasOwn(userAliases, model) || userPriceOverrides.has(model)) return model - return Date.parse(timestamp ?? '') < AUTO_REVIEW_LUNA_FROM ? 'gpt-5.4' : model + const id = model.toLowerCase() + if (id !== 'codex-auto-review' && id !== 'kimi-for-coding' && id !== 'kimi-code') return model + if (Object.hasOwn(userAliases, model) || userPriceOverrides.has(model)) return model + const at = Date.parse(timestamp ?? '') + if (id === 'codex-auto-review') return at < AUTO_REVIEW_LUNA_FROM ? 'gpt-5.4' : model + if (at < KIMI_CODING_K2_5_FROM) return 'kimi-k2-thinking' + if (at < KIMI_CODING_K2_6_FROM) return 'kimi-k2.5' + return at < KIMI_CODING_K2_7_FROM ? 'kimi-k2.6' : model +} + +/// True when pricingModelAt stands in for a model with no published rate: +/// Kimi Code's alias served K2.8 Preview from 11 Sep 2026, priced as K2.7 Code. +/// A missing or unparseable timestamp gets the forward default, so it counts. +export function isStandInPricedAt(model: string, timestamp: string | undefined): boolean { + const id = model.toLowerCase() + if (id !== 'kimi-for-coding' && id !== 'kimi-code') return false + if (Object.hasOwn(userAliases, model) || userPriceOverrides.has(model)) return false + const at = Date.parse(timestamp ?? '') + return !(at < KIMI_CODING_K2_8_FROM) } export function getModelCosts(model: string): ModelCosts | null { @@ -1548,6 +1582,8 @@ const autoModelNames: Record = { 'openclaw-auto': 'OpenClaw (auto)', 'qwen-auto': 'Qwen (auto)', 'kimi-auto': 'Kimi (auto)', + 'kimi-for-coding': 'Kimi for Coding', + 'kimi-for-coding-highspeed': 'Kimi for Coding HighSpeed', 'codex-auto-review': 'Codex Auto Review', 'gpt-reserve': 'Luna Reserve', } diff --git a/src/parser.ts b/src/parser.ts index e76a3942..df98994d 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -5,7 +5,7 @@ import { createHash } from 'crypto' import { performance } from 'node:perf_hooks' import { basename, dirname, join, resolve, sep } from 'path' import { FS_SCAN_CONCURRENCY, mapWithConcurrency, readSessionLines } from './fs-utils.js' -import { billableOutputTokens, calculateCost, calculateLocalModelSavings, getShortModelName, modelRowKey, pricingModelAt, isProxiedPath, getProxyPathsConfigHash, getModelAliasesConfigHash, getPriceOverridesConfigHash, getLocalModelSavingsConfigHash, recordedCostFallback, getModelRoute } from './models.js' +import { billableOutputTokens, calculateCost, calculateLocalModelSavings, getShortModelName, modelRowKey, pricingModelAt, isProxiedPath, getProxyPathsConfigHash, getModelAliasesConfigHash, getPriceOverridesConfigHash, getLocalModelSavingsConfigHash, recordedCostFallback, getModelRoute, isStandInPricedAt } from './models.js' import { resolveSubagentAttribution, sessionIdentity } from './sessions-report.js' import { normalizeContentBlocks, flatSlice, flatString } from './content-utils.js' import { discoverAllSessions, discoverAllSessionsWithFailures, getProvider } from './providers/index.js' @@ -2869,7 +2869,7 @@ function cachedCallToApiCall(call: CachedCall): ParsedApiCall { webSearchRequests: u.webSearchRequests, }, costUSD: call.costUSD ?? recordedCostFallback(call.model, costUSD, call.fallbackCostUSD) ?? costUSD, - isEstimated: call.isEstimated, + isEstimated: call.isEstimated || (call.costUSD === undefined && isStandInPricedAt(call.model, call.timestamp)) || undefined, tools: call.tools, mcpTools: extractMcpTools(call.tools), skills: call.skills, diff --git a/src/providers/kimi.ts b/src/providers/kimi.ts index ceb98519..ae6ddb00 100644 --- a/src/providers/kimi.ts +++ b/src/providers/kimi.ts @@ -5,7 +5,7 @@ import { homedir } from 'os' import { extractBashCommands } from '../bash-utils.js' import { readSessionLines } from '../fs-utils.js' -import { calculateCost, getShortModelName } from '../models.js' +import { calculateCost, getShortModelName, pricingModelAt } from '../models.js' import type { ProbeRoot, ParsedProviderCall, Provider, SessionParser, SessionSource } from './types.js' type JsonObject = Record @@ -298,7 +298,7 @@ function createParser(source: SessionSource, shareDir: string, seenKeys: Set): SessionPars reasoningTokens: 0, webSearchRequests: 0, costUSD: calculateCost( - realModel, + pricingModelAt(realModel, timestamp), inputTokens, outputTokens, cacheCreationInputTokens, diff --git a/tests/kimi-stand-in-estimated.test.ts b/tests/kimi-stand-in-estimated.test.ts new file mode 100644 index 00000000..bfd9599c --- /dev/null +++ b/tests/kimi-stand-in-estimated.test.ts @@ -0,0 +1,57 @@ +// The K2.8 Preview stand-in flag is set where every call is read +// (cachedCallToApiCall), so it reaches calls from any provider, cold or warm. +// Own file because the codex provider captures CODEX_HOME at import. + +import { afterAll, beforeEach, expect, it, vi } from 'vitest' +import { mkdir, rm, writeFile } from 'fs/promises' +import { join } from 'path' + +const testRoot = vi.hoisted(() => { + const root = `${process.env['TMPDIR'] || '/tmp'}/kimi-stand-in-${process.pid}-${Date.now()}` + process.env['HOME'] = `${root}/home` + process.env['USERPROFILE'] = `${root}/home` + process.env['CODEX_HOME'] = `${root}/codex` + return root +}) + +const CODEX_HOME = join(testRoot, 'codex') +const CACHE_DIR = join(testRoot, 'cache') + +beforeEach(() => { + process.env['HOME'] = join(testRoot, 'home') + process.env['USERPROFILE'] = join(testRoot, 'home') + process.env['CODEX_HOME'] = CODEX_HOME + process.env['CODEBURN_CACHE_DIR'] = CACHE_DIR +}) + +afterAll(async () => { + await rm(testRoot, { recursive: true, force: true }) +}) + +function rollout(id: string, day: string): string { + const usage = { input_tokens: 1000, cached_input_tokens: 0, output_tokens: 100, reasoning_output_tokens: 0, total_tokens: 1100 } + return [ + JSON.stringify({ type: 'session_meta', timestamp: `${day}T10:00:00Z`, payload: { session_id: id, model: 'kimi-for-coding', cwd: '/Users/test/proj', originator: 'codex_cli_rs' } }), + JSON.stringify({ type: 'response_item', timestamp: `${day}T10:00:10Z`, payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'hi' }] } }), + JSON.stringify({ type: 'event_msg', timestamp: `${day}T10:01:00Z`, payload: { type: 'token_count', info: { model: 'kimi-for-coding', last_token_usage: usage, total_token_usage: usage } } }), + ].join('\n') + '\n' +} + +it('marks kimi-for-coding calls from 11 Sep 2026 estimated on a cold parse and a cache-rehydrated read', async () => { + await mkdir(join(CODEX_HOME, 'sessions', '2026', '09', '10'), { recursive: true }) + await mkdir(join(CODEX_HOME, 'sessions', '2026', '09', '12'), { recursive: true }) + await mkdir(CACHE_DIR, { recursive: true }) + await writeFile(join(CODEX_HOME, 'sessions', '2026', '09', '10', 'rollout-k27.jsonl'), rollout('k27', '2026-09-10')) + await writeFile(join(CODEX_HOME, 'sessions', '2026', '09', '12', 'rollout-k28.jsonl'), rollout('k28', '2026-09-12')) + + const { clearSessionCache, parseAllSessions } = await import('../src/parser.js') + const flags = (projects: Awaited>) => projects + .flatMap(p => p.sessions).flatMap(s => s.turns).flatMap(t => t.assistantCalls) + .map(c => [c.timestamp.slice(0, 10), c.isEstimated ?? false]) + .sort() + + clearSessionCache() + expect(flags(await parseAllSessions(undefined, 'codex'))).toEqual([['2026-09-10', false], ['2026-09-12', true]]) + clearSessionCache() + expect(flags(await parseAllSessions(undefined, 'codex'))).toEqual([['2026-09-10', false], ['2026-09-12', true]]) +}) diff --git a/tests/menubar-json.test.ts b/tests/menubar-json.test.ts index 4e1e9279..2ad2862a 100644 --- a/tests/menubar-json.test.ts +++ b/tests/menubar-json.test.ts @@ -208,7 +208,7 @@ describe('buildMenubarPayload', () => { const kimiK3 = payload.current.topModels.find(m => m.name === 'Kimi K3')! expect(kimiK3.cost).toBeCloseTo(2.5 + 0.5 + 1.2) expect(kimiK3.calls).toBe(78 + 2 + 40) - const k2 = payload.current.topModels.find(m => m.name === 'Kimi K2 Thinking')! + const k2 = payload.current.topModels.find(m => m.name === 'Kimi for Coding')! expect(k2.cost).toBeCloseTo(0.06) expect(payload.current.topModels.find(m => m.name === 'k3')).toBeUndefined() expect(payload.current.topModels.find(m => m.name === 'k3-agent')).toBeUndefined() diff --git a/tests/models-hoist.test.ts b/tests/models-hoist.test.ts index 324e6ff6..68c28772 100644 --- a/tests/models-hoist.test.ts +++ b/tests/models-hoist.test.ts @@ -92,7 +92,7 @@ describe('post-hoist resolution stability', () => { it('kimi managed aliases resolve to priced Kimi models', () => { expect(getShortModelName('kimi-auto')).toBe('Kimi (auto)') - expect(getShortModelName('kimi-for-coding')).toBe('Kimi K2 Thinking') + expect(getShortModelName('kimi-for-coding')).toBe('Kimi for Coding') expect(getShortModelName('kimi-k2-thinking-turbo')).toBe('Kimi K2 Thinking Turbo') expect(getShortModelName('kimi-k2.6')).toBe('Kimi K2.6') expect(getModelCosts('kimi-auto')?.inputCostPerToken).toBeGreaterThan(0) diff --git a/tests/models.test.ts b/tests/models.test.ts index 8c125e83..e5d4acf1 100644 --- a/tests/models.test.ts +++ b/tests/models.test.ts @@ -30,6 +30,7 @@ import { snapshotPricingState, restorePricingState, pricingModelAt, + isStandInPricedAt, } from '../src/models.js' import { getDailyCacheConfigHash } from '../src/usage-aggregator.js' import snapshotData from '../src/data/litellm-snapshot.json' with { type: 'json' } @@ -572,7 +573,7 @@ describe('resolveCanonicalModelId', () => { expect(resolveCanonicalModelId('gpt-5-fast')).toBe('gpt-5') expect(resolveCanonicalModelId('gpt-5-untracked-xyz')).toBe('gpt-5-untracked-xyz') expect(resolveCanonicalModelId('claude-opus-4.6')).toBe('claude-opus-4-6') - expect(resolveCanonicalModelId('kimi-code')).toBe('kimi-k2-thinking') + expect(resolveCanonicalModelId('kimi-code')).toBe('kimi-k2.7-code') expect(resolveCanonicalModelId('cline-pass/kimi-k3')).toBe('kimi-k3') expect(resolveCanonicalModelId('orcarouter/auto')).not.toBe(resolveCanonicalModelId('claude-sonnet-4-5')) expect(resolveCanonicalModelId('orcarouter/fusion')).toBe(resolveCanonicalModelId('openai/gpt-oss-120b')) @@ -1196,6 +1197,79 @@ describe('Cursor model variants resolve to pricing', () => { }) }) +describe('Kimi Code moving alias', () => { + const rates = (model: string) => { + const c = getModelCosts(model)! + return [c.inputCostPerToken * 1e6, c.cacheReadCostPerToken * 1e6, c.outputCostPerToken * 1e6].map(v => +v.toFixed(4)) + } + + it('prices kimi-for-coding by the model it served on the call date', () => { + expect(pricingModelAt('kimi-for-coding', '2026-01-26T23:59:59.999Z')).toBe('kimi-k2-thinking') + expect(pricingModelAt('kimi-for-coding', '2026-01-27T00:00:00.000Z')).toBe('kimi-k2.5') + expect(pricingModelAt('kimi-for-coding', '2026-04-12T23:59:59.999Z')).toBe('kimi-k2.5') + expect(pricingModelAt('kimi-for-coding', '2026-04-13T00:00:00.000Z')).toBe('kimi-k2.6') + expect(pricingModelAt('kimi-for-coding', '2026-06-11T23:59:59.999Z')).toBe('kimi-k2.6') + expect(pricingModelAt('kimi-for-coding', '2026-06-12T00:00:00.000Z')).toBe('kimi-for-coding') + expect(pricingModelAt('kimi-for-coding', '2026-09-27T10:00:00Z')).toBe('kimi-for-coding') + expect(pricingModelAt('kimi-code', '2026-03-01T00:00:00Z')).toBe('kimi-k2.5') + expect(pricingModelAt('kimi-for-coding', undefined)).toBe('kimi-for-coding') + expect(pricingModelAt('kimi-for-coding', 'not a date')).toBe('kimi-for-coding') + expect(pricingModelAt('kimi-for-coding-highspeed', '2026-03-01T00:00:00Z')).toBe('kimi-for-coding-highspeed') + expect(pricingModelAt('k3', '2026-03-01T00:00:00Z')).toBe('k3') + }) + + it('resolves each period to Moonshot list prices ($/M input, cache hit, output)', () => { + expect(rates('kimi-k2.5')).toEqual([0.6, 0.1, 3]) + expect(rates('kimi-k2.6')).toEqual([0.95, 0.16, 4]) + expect(rates('kimi-for-coding')).toEqual([0.95, 0.19, 4]) + expect(rates('kimi-code')).toEqual([0.95, 0.19, 4]) + expect(rates('kimi-for-coding-highspeed')).toEqual([1.9, 0.38, 8]) + expect(rates('k3')).toEqual([3, 0.3, 15]) + }) + + it('treats highspeed as priced, not a flat-rate SKU', () => { + expect(isFlatRateModel('kimi-for-coding-highspeed')).toBe(false) + expect(calculateCost('kimi-for-coding-highspeed', 1_000_000, 1_000_000, 0, 1_000_000, 0)).toBeCloseTo(10.28) + }) + + it('marks only the K2.8 Preview period (from 11 Sep 2026) as stand-in priced', () => { + expect(isStandInPricedAt('kimi-for-coding', '2026-09-10T23:59:59.999Z')).toBe(false) + expect(isStandInPricedAt('kimi-for-coding', '2026-09-11T00:00:00.000Z')).toBe(true) + expect(isStandInPricedAt('kimi-code', '2026-09-27T10:00:00Z')).toBe(true) + expect(isStandInPricedAt('kimi-for-coding', '2026-07-01T00:00:00Z')).toBe(false) + expect(isStandInPricedAt('kimi-for-coding', '2026-03-01T00:00:00Z')).toBe(false) + expect(isStandInPricedAt('kimi-for-coding', undefined)).toBe(true) + expect(isStandInPricedAt('kimi-for-coding-highspeed', '2026-09-27T10:00:00Z')).toBe(false) + expect(isStandInPricedAt('k3', '2026-09-27T10:00:00Z')).toBe(false) + setModelAliases({ 'kimi-for-coding': 'kimi-k3' }) + try { + expect(isStandInPricedAt('kimi-for-coding', '2026-09-27T10:00:00Z')).toBe(false) + } finally { + setModelAliases({}) + } + }) + + it('keeps the alias name for display', () => { + expect(getShortModelName('kimi-for-coding')).toBe('Kimi for Coding') + expect(getShortModelName('kimi-for-coding-highspeed')).toBe('Kimi for Coding HighSpeed') + }) + + it('lets a user alias or price override win over the date rule', () => { + setModelAliases({ 'kimi-for-coding': 'kimi-k3' }) + try { + expect(pricingModelAt('kimi-for-coding', '2026-03-01T00:00:00Z')).toBe('kimi-for-coding') + } finally { + setModelAliases({}) + } + setPriceOverrides({ 'kimi-for-coding': { input: 1, output: 2 } }) + try { + expect(pricingModelAt('kimi-for-coding', '2026-03-01T00:00:00Z')).toBe('kimi-for-coding') + } finally { + setPriceOverrides({}) + } + }) +}) + describe('Codex activity ids (#1047)', () => { it('keeps the activity label instead of collapsing to the underlying model name', () => { expect(getShortModelName('codex-auto-review')).toBe('Codex Auto Review') @@ -1786,8 +1860,6 @@ describe('findUnpricedModels', () => { { model: 'auto-genius', calls: 898, cost: 0, tokens: 35_300_000 }, { model: 'cline-pass/auto-genius', calls: 4, cost: 0, tokens: 33_900 }, { model: 'auto', calls: 449, cost: 0, tokens: 17_700_000 }, - { model: 'kimi-for-coding-highspeed', calls: 12, cost: 0, tokens: 3_400_000 }, - { model: 'moonshot/kimi-for-coding-highspeed', calls: 2, cost: 0, tokens: 80_000 }, { model: 'grok-composer-2.5-fast', calls: 10, cost: 0, tokens: 1_900_000 }, { model: 'Grok Composer 2.5 Fast', calls: 10, cost: 0, tokens: 1_900_000 }, { model: 'Warp Auto (efficient)', calls: 3, cost: 0, tokens: 50_000 }, @@ -1827,7 +1899,7 @@ describe('findUnpricedModels', () => { expect(isExpectedFreeModel('warp-auto-efficient')).toBe(false) expect(isExpectedFreeModel('auto-genius')).toBe(true) expect(isExpectedFreeModel('auto')).toBe(true) - expect(isExpectedFreeModel('kimi-for-coding-highspeed')).toBe(true) + expect(isExpectedFreeModel('kimi-for-coding-highspeed')).toBe(false) expect(isExpectedFreeModel('warp')).toBe(false) expect(isExpectedFreeModel('codex-auto-review')).toBe(false) expect(isExpectedFreeModel('zz-mystery-paid-model-999')).toBe(false) diff --git a/tests/providers/kimi.test.ts b/tests/providers/kimi.test.ts index 486a03ee..eab0de6c 100644 --- a/tests/providers/kimi.test.ts +++ b/tests/providers/kimi.test.ts @@ -125,6 +125,32 @@ describe('Kimi provider', () => { expect(calls[0]!.costUSD).toBeGreaterThan(0) }) + it('prices kimi-for-coding by call date', async () => { + const wirePath = await writeSession('/Users/test/work/app', 'sess-k28', [ + record(1789084799, 'StatusUpdate', { + message_id: 'before', + model_name: 'kimi-for-coding', + token_usage: { input_other: 1_000_000, output: 1_000_000 }, + }), + record(1789084800, 'StatusUpdate', { + message_id: 'from', + model_name: 'kimi-for-coding', + token_usage: { input_other: 1_000_000, output: 1_000_000 }, + }), + record(1777593600, 'StatusUpdate', { + message_id: 'k26', + model_name: 'kimi-for-coding', + token_usage: { input_other: 1_000_000, output: 0 }, + }), + ]) + + const calls = await collect(createKimiProvider(tmpDir), wirePath) + + expect(calls[0]!.costUSD).toBeCloseTo(4.95) + expect(calls[1]!.costUSD).toBeCloseTo(4.95) + expect(calls[2]!.costUSD).toBeCloseTo(0.95) + }) + it('uses content parts, model payload overrides, and message-id deduplication', async () => { process.env.KIMI_MODEL_NAME = 'kimi-k2-thinking' const wirePath = await writeSession('/Users/test/work/app', 'sess-2', [