theokit 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/{actions-virtual-module-E6PU47GV.js → actions-virtual-module-HWNTIEPI.js} +40 -6
- package/dist/actions-virtual-module-HWNTIEPI.js.map +1 -0
- package/dist/{actions-virtual-module-E7IELVHL.js → actions-virtual-module-SEIPACG5.js} +40 -6
- package/dist/actions-virtual-module-SEIPACG5.js.map +1 -0
- package/dist/add-2ZFFGGE4.js +0 -0
- package/dist/{agent-events-lSIS9uYs.d.ts → agent-events-B3KNV9ER.d.ts} +1 -1
- package/dist/{app-typed-client-YIBP6JWH.js → app-typed-client-6GC6VWXS.js} +11 -19
- package/dist/app-typed-client-6GC6VWXS.js.map +1 -0
- package/dist/{app-typed-client-RZMVWHJ3.js → app-typed-client-US2ZXBEC.js} +10 -18
- package/dist/app-typed-client-US2ZXBEC.js.map +1 -0
- package/dist/aws-lambda-XYCGZH3I.js +0 -0
- package/dist/broadcast-QOQGUNNK.js +0 -0
- package/dist/{build-NCGIV4LL.js → build-MREW6MVS.js} +7 -7
- package/dist/{build-NCGIV4LL.js.map → build-MREW6MVS.js.map} +1 -1
- package/dist/build-request-body-preview-II2235E6.js +0 -0
- package/dist/bun-K73TQEWC.js +0 -0
- package/dist/check-PZR67OY5.js +0 -0
- package/dist/chunk-5ODOE6EF.js +0 -0
- package/dist/{chunk-MOT42NAH.js → chunk-5OFPQK6N.js} +18 -3
- package/dist/chunk-5OFPQK6N.js.map +1 -0
- package/dist/{chunk-M6MYZSD2.js → chunk-5OKZY2VL.js} +29 -29
- package/dist/{chunk-M6MYZSD2.js.map → chunk-5OKZY2VL.js.map} +1 -1
- package/dist/{chunk-M23FP2AS.js → chunk-64JI5LTO.js} +1 -1
- package/dist/{chunk-M23FP2AS.js.map → chunk-64JI5LTO.js.map} +1 -1
- package/dist/{chunk-7DHGV77J.js → chunk-7TOMTMHT.js} +18 -3
- package/dist/chunk-7TOMTMHT.js.map +1 -0
- package/dist/{chunk-SEVYARIU.js → chunk-BE2LKDI7.js} +3 -3
- package/dist/{chunk-OJHO2X7C.js → chunk-CZM6WWWS.js} +1 -1
- package/dist/{chunk-OJHO2X7C.js.map → chunk-CZM6WWWS.js.map} +1 -1
- package/dist/chunk-F3TG5FJV.js +0 -0
- package/dist/chunk-KT3MWNZG.js +0 -0
- package/dist/chunk-O7335ZGH.js +0 -0
- package/dist/{chunk-QLGES2HC.js → chunk-SNDZQ64K.js} +2 -2
- package/dist/{chunk-C27AZN46.js → chunk-TPCT3MXV.js} +1 -1
- package/dist/{chunk-C27AZN46.js.map → chunk-TPCT3MXV.js.map} +1 -1
- package/dist/chunk-WZ7CPVQB.js +0 -0
- package/dist/{chunk-EXJWO2HB.js → chunk-X2OLD264.js} +29 -29
- package/dist/{chunk-EXJWO2HB.js.map → chunk-X2OLD264.js.map} +1 -1
- package/dist/{chunk-ZBWROIIO.js → chunk-X4JAX2U3.js} +3 -3
- package/dist/{chunk-ZBWROIIO.js.map → chunk-X4JAX2U3.js.map} +1 -1
- package/dist/chunk-XMS5VRIK.js +0 -0
- package/dist/{chunk-MG7H3LAW.js → chunk-XP7YXRHO.js} +2 -2
- package/dist/{chunk-ERAZNHHR.js → chunk-YWWHK7R6.js} +2 -2
- package/dist/chunk-ZJW5FFYJ.js +0 -0
- package/dist/chunk-ZOXNJV2O.js +0 -0
- package/dist/cli/index.js +7 -7
- package/dist/client/index.d.ts +2 -2
- package/dist/cloudflare-LCAOTRYZ.js +0 -0
- package/dist/configure-agent-registry-6YGTJYBC.js +0 -0
- package/dist/deno-deploy-ZN4RE5HF.js +0 -0
- package/dist/{dev-WDXC74CK.js → dev-6P2DM33Q.js} +7 -7
- package/dist/{dev-WDXC74CK.js.map → dev-6P2DM33Q.js.map} +1 -1
- package/dist/{dev-emit-3CHM73TV.js → dev-emit-MLDFOLUU.js} +3 -3
- package/dist/{dev-emit-I3CKOHML.js → dev-emit-PRSRPDHA.js} +4 -4
- package/dist/dispatcher-E6QV32BO.js +0 -0
- package/dist/{dist-R2Q7GVDW.js → dist-E6BL4ZVY.js} +9 -9
- package/dist/dist-E6BL4ZVY.js.map +1 -0
- package/dist/docker-EZDA5FT7.js +0 -0
- package/dist/generate-DT4IIAHF.js +0 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/{info-VM7EK4JC.js → info-FJ4NXKTB.js} +3 -3
- package/dist/{lib-NST3PNZX.js → lib-NL5JXGEB.js} +31 -31
- package/dist/lib-NL5JXGEB.js.map +1 -0
- package/dist/netlify-5VQ22Z2P.js +0 -0
- package/dist/node-3APTLGWB.js +0 -0
- package/dist/{openapi-CBASKC7W.js → openapi-SOKZTFE3.js} +6 -6
- package/dist/{proper-lockfile-MVKMQGFK.js → proper-lockfile-4Y3DP3RP.js} +27 -27
- package/dist/proper-lockfile-4Y3DP3RP.js.map +1 -0
- package/dist/registry-5QFTLOL2.js +0 -0
- package/dist/{routes-7O4NUDYS.js → routes-XVRAUH6H.js} +5 -5
- package/dist/scan-C2BOKLSY.js +0 -0
- package/dist/{schema-JAKF23GR.js → schema-VR6YNVLQ.js} +2 -2
- package/dist/server/cost/index.d.ts +1 -1
- package/dist/server/cost/index.js +1 -1
- package/dist/server/index.d.ts +22 -12
- package/dist/server/index.js +7 -7
- package/dist/server/index.js.map +1 -1
- package/dist/services-typed-client-ZX5JUHH3.js +0 -0
- package/dist/{sqlite-vec-54KB4RD3.js → sqlite-vec-ARPNUBWT.js} +2 -2
- package/dist/sqlite-vec-ARPNUBWT.js.map +1 -0
- package/dist/{start-5JTFWF4T.js → start-R7XSQL5L.js} +10 -10
- package/dist/{start-5JTFWF4T.js.map → start-R7XSQL5L.js.map} +1 -1
- package/dist/static-TZBVBDTB.js +0 -0
- package/dist/storage-manager-D5KBXXKB.js +0 -0
- package/dist/theo-cloud-3K4DGB3S.js +0 -0
- package/dist/{upgrade-readiness-Q46KTG2J.js → upgrade-readiness-GUJBCJIS.js} +5 -5
- package/dist/{upgrade-readiness-Q46KTG2J.js.map → upgrade-readiness-GUJBCJIS.js.map} +1 -1
- package/dist/vercel-MWW24ABC.js +0 -0
- package/dist/vite-plugin/index.d.ts +2 -2
- package/dist/vite-plugin/index.js +3 -3
- package/dist/{vite-plugin-4WZ3C2AC.js → vite-plugin-6MPHTKIW.js} +7 -7
- package/package.json +9 -9
- package/dist/actions-virtual-module-E6PU47GV.js.map +0 -1
- package/dist/actions-virtual-module-E7IELVHL.js.map +0 -1
- package/dist/app-typed-client-RZMVWHJ3.js.map +0 -1
- package/dist/app-typed-client-YIBP6JWH.js.map +0 -1
- package/dist/chunk-7DHGV77J.js.map +0 -1
- package/dist/chunk-MOT42NAH.js.map +0 -1
- package/dist/dist-R2Q7GVDW.js.map +0 -1
- package/dist/lib-NST3PNZX.js.map +0 -1
- package/dist/proper-lockfile-MVKMQGFK.js.map +0 -1
- package/dist/sqlite-vec-54KB4RD3.js.map +0 -1
- /package/dist/{chunk-SEVYARIU.js.map → chunk-BE2LKDI7.js.map} +0 -0
- /package/dist/{chunk-QLGES2HC.js.map → chunk-SNDZQ64K.js.map} +0 -0
- /package/dist/{chunk-MG7H3LAW.js.map → chunk-XP7YXRHO.js.map} +0 -0
- /package/dist/{chunk-ERAZNHHR.js.map → chunk-YWWHK7R6.js.map} +0 -0
- /package/dist/{dev-emit-3CHM73TV.js.map → dev-emit-MLDFOLUU.js.map} +0 -0
- /package/dist/{dev-emit-I3CKOHML.js.map → dev-emit-PRSRPDHA.js.map} +0 -0
- /package/dist/{info-VM7EK4JC.js.map → info-FJ4NXKTB.js.map} +0 -0
- /package/dist/{openapi-CBASKC7W.js.map → openapi-SOKZTFE3.js.map} +0 -0
- /package/dist/{routes-7O4NUDYS.js.map → routes-XVRAUH6H.js.map} +0 -0
- /package/dist/{schema-JAKF23GR.js.map → schema-VR6YNVLQ.js.map} +0 -0
- /package/dist/{vite-plugin-4WZ3C2AC.js.map → vite-plugin-6MPHTKIW.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/cost/track-agent-run.ts","../src/server/cost/usage-storage-memory.ts","../src/server/cost/track-agent-tools.ts"],"sourcesContent":["import type { AgentRunRecord } from '../../devtools/shared.js'\n\nimport type { UsageRecord, UsageStorageAdapter } from './cost-types.js'\n\nexport interface TrackAgentRunInput {\n userId: string\n model: string\n tokens: { input: number; output: number }\n costUsd: number\n /** Defaults to `new Date()`. */\n timestamp?: Date\n /** Optional execution status surfaced to devtools (default 'finished'). */\n status?: 'finished' | 'error' | 'aborted'\n}\n\nexport interface TrackAgentRunOptions {\n /** Adapter resolved from `theo.config.ts > cost.storage`. */\n storage: UsageStorageAdapter | undefined\n}\n\n// v1.1 EC-4 MUST FIX — universal dev gate (Vite OR tsup-bundled).\n// Both checks are statically replaceable by bundlers in prod build,\n// so the entire dispatcher import is tree-shaken from the prod bundle.\nconst __IS_DEV = (() => {\n try {\n return (import.meta as { env?: { DEV?: boolean } }).env?.DEV === true\n } catch {\n return process.env.NODE_ENV !== 'production'\n }\n})()\n\n/**\n * Record a single agent run's usage + cost. Companion to the client-side\n * `<CostMeter>` from `@usetheo/ui`.\n *\n * EC-14: this function NEVER bubbles errors back to the caller. Adapter\n * failures (network outage, DB down, etc.) are logged via `console.warn`\n * and swallowed. The agent response MUST NOT fail because cost tracking\n * is degraded.\n *\n * No-op when `storage` is undefined (cost tracking unconfigured).\n *\n * In dev mode (Vite OR tsup-built with NODE_ENV != 'production'), fires a\n * `theokit-evolution-ci-and-dx` Phase 3 dispatcher event so the devtools\n * Agents tab can render the run. Prod tree-shakes the entire dispatcher\n * import via the `__IS_DEV` IIFE guard.\n *\n * @see docs/concepts/cost-tracking.md (when implemented in T6.3)\n */\nexport async function trackAgentRun(\n input: TrackAgentRunInput,\n opts: TrackAgentRunOptions,\n): Promise<void> {\n // Dev-only: surface run to devtools Agents tab (T3.1)\n if (__IS_DEV) {\n try {\n const mod = (await import('../../devtools/dispatcher.js')) as {\n dispatcher: { onAgentRun: (r: AgentRunRecord) => void }\n }\n mod.dispatcher.onAgentRun({\n // eslint-disable-next-line sonarjs/pseudo-random -- non-secret correlation id\n id: `run-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`,\n timestamp: (input.timestamp ?? new Date()).getTime(),\n userId: input.userId,\n model: input.model,\n tokensInput: input.tokens.input,\n tokensOutput: input.tokens.output,\n costUsd: input.costUsd,\n status: input.status ?? 'finished',\n })\n } catch {\n // devtools dispatcher missing in some prod-like bundle — silently skip\n }\n }\n\n if (!opts.storage) return\n const record: UsageRecord = {\n userId: input.userId,\n model: input.model,\n tokens: input.tokens,\n costUsd: input.costUsd,\n timestamp: input.timestamp ?? new Date(),\n }\n try {\n await opts.storage.record(record)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n\n console.warn(\n `[theokit:cost] usage storage record failed: ${message}. ` +\n '(Response is unaffected — cost tracking degraded.)',\n )\n }\n}\n","import type { StorageAdapter } from '../storage/storage-types.js'\n\nimport type {\n ToolUsageRecord,\n UsageQuery,\n UsageRecord,\n UsageResult,\n UsageStorageAdapter,\n} from './cost-types.js'\n\n/**\n * In-memory usage storage for dev/tests. Unbounded — production\n * deployments MUST plug in a durable adapter (Postgres/Redis recipes\n * land with R0.6.7; documented in EC-114).\n *\n * Phase 5 — Production-Readiness #4: accepts both `UsageRecord` (LLM call,\n * kind='llm' or omitted) AND `ToolUsageRecord` (tool call, kind='tool').\n * `getUsage` only sums LLM records (tools have no token/cost dimension).\n *\n * EC-9 (backward compat): input without `kind` is normalized to `kind:'llm'`\n * — adapters from older versions stay working.\n *\n * Concurrency: Node's single-threaded event loop guarantees that\n * `Array.push` is atomic, so concurrent record() calls cannot lose data.\n */\nexport class InMemoryUsageStorage implements UsageStorageAdapter, StorageAdapter {\n readonly name = 'memory'\n readonly #records: (UsageRecord | ToolUsageRecord)[] = []\n\n /**\n * T2.3 — `StorageAdapter` lifecycle hook (ADR-0007 D6). In-memory storage\n * has no real cleanup to perform — this is intentionally a noop so the\n * adapter can be registered with `StorageManager.register()` and\n * participate in graceful shutdown without changing behavior.\n *\n * Does NOT clear stored records (dispose ≠ reset). For test reset use\n * a fresh instance.\n */\n dispose(): Promise<void> {\n // Intentional noop — see jsdoc above\n return Promise.resolve()\n }\n\n async record(input: UsageRecord | ToolUsageRecord): Promise<void> {\n // EC-9: normalize legacy input (no `kind` field) to kind:'llm'.\n const normalized: UsageRecord | ToolUsageRecord =\n 'kind' in input && input.kind === 'tool'\n ? { ...input }\n : ({ ...input, kind: 'llm' as const } satisfies UsageRecord)\n this.#records.push(normalized)\n return Promise.resolve()\n }\n\n async getUsage(query: UsageQuery): Promise<UsageResult> {\n const from = query.period.from.getTime()\n const to = query.period.to.getTime()\n let totalTokens = 0\n let totalCostUsd = 0\n let runs = 0\n for (const r of this.#records) {\n if (r.kind === 'tool') continue\n if (r.userId !== query.userId) continue\n const t = r.timestamp.getTime()\n if (t < from || t > to) continue\n totalTokens += r.tokens.input + r.tokens.output\n totalCostUsd += r.costUsd\n runs += 1\n }\n return Promise.resolve({ totalTokens, totalCostUsd, runs })\n }\n\n /** @internal — test helper to inspect raw record stream */\n __getRecords(): readonly (UsageRecord | ToolUsageRecord)[] {\n return this.#records\n }\n}\n","import type { ToolUsageRecord, UsageStorageAdapter } from './cost-types.js'\n\n/**\n * Phase 5 — Production-Readiness #4: tool lifecycle hooks for cost tracking.\n *\n * The SDK calls `onToolStart` → handler runs → `onToolEnd` (success) or\n * `onToolError` (throw). This factory returns the three callbacks ready to\n * attach to `Agent.create({ tools, onToolStart, onToolEnd, onToolError })`.\n *\n * Each successful tool call writes a `ToolUsageRecord{success:true}`; each\n * failure writes `ToolUsageRecord{success:false}` with `errorMessage` from\n * the thrown Error.\n *\n * Invariants:\n * - `onToolStart` records nothing yet (no duration available). Only stashes\n * a `startedAt` timestamp by `callId` in a per-factory Map.\n * - `onToolEnd` reads the stashed timestamp, computes `durationMs`, writes\n * a successful record. Removes the Map entry.\n * - `onToolError` analog with `success:false`.\n * - Hook throws are SWALLOWED to stderr — observation hooks must not crash\n * the run (per the v1.1.0 release contract).\n * - EC-8 (SHOULD TEST) — orphan starts (no matching End/Error) are pruned\n * on every onToolStart if older than 5 minutes. Bounded memory.\n * - EC-16 (DOCUMENT) — callId uniqueness is SDK contract; duplicate Start\n * for the same callId uses last-write-wins.\n */\n\nconst ORPHAN_TTL_MS = 5 * 60 * 1000\n\ninterface PendingStart {\n startedAt: number\n userId: string\n conversationId: string\n}\n\nexport interface TrackAgentToolsOptions {\n storage: UsageStorageAdapter\n /** Defaults to () => new Date() — overridable for deterministic testing. */\n now?: () => Date\n /** Identifier for the user — passed through to ToolUsageRecord.userId. */\n userId?: string\n /** Identifier for the conversation — passed through to ToolUsageRecord.conversationId. */\n conversationId?: string\n}\n\nexport interface ToolHookEvent {\n callId: string\n name: string\n // SDK shape — we only read these structurally\n [key: string]: unknown\n}\n\nexport interface TrackAgentToolsHooks {\n onToolStart: (event: ToolHookEvent) => void\n onToolEnd: (event: ToolHookEvent) => void\n onToolError: (event: ToolHookEvent) => void\n}\n\n/**\n * Create the three tool-lifecycle callbacks. Hand the returned object's\n * methods to `Agent.create({ tools, onToolStart, onToolEnd, onToolError })`.\n */\nexport function trackAgentTools(opts: TrackAgentToolsOptions): TrackAgentToolsHooks {\n const pending = new Map<string, PendingStart>()\n const now = opts.now ?? ((): Date => new Date())\n const userId = opts.userId ?? 'anonymous'\n const conversationId = opts.conversationId ?? 'unknown'\n\n function pruneOrphans(): void {\n const cutoff = now().getTime() - ORPHAN_TTL_MS\n for (const [id, p] of pending) {\n if (p.startedAt < cutoff) pending.delete(id)\n }\n }\n\n function safeRecord(record: ToolUsageRecord): void {\n Promise.resolve(opts.storage.record(record)).catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(`[theokit:trackAgentTools] storage.record failed: ${msg}`)\n })\n }\n\n return {\n onToolStart(event: ToolHookEvent): void {\n try {\n pruneOrphans()\n pending.set(event.callId, {\n startedAt: now().getTime(),\n userId,\n conversationId,\n })\n } catch (err) {\n console.warn(\n `[theokit:trackAgentTools] onToolStart hook crashed (swallowed): ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n },\n onToolEnd(event: ToolHookEvent): void {\n try {\n const start = pending.get(event.callId)\n const startedAt = start?.startedAt ?? now().getTime()\n const durationMs = start ? now().getTime() - startedAt : 0\n pending.delete(event.callId)\n if (!start) {\n console.warn(\n `[theokit:trackAgentTools] orphan onToolEnd for callId=${event.callId}; durationMs:0 recorded`,\n )\n }\n safeRecord({\n kind: 'tool',\n userId: start?.userId ?? userId,\n conversationId: start?.conversationId ?? conversationId,\n toolName: event.name,\n callId: event.callId,\n success: true,\n durationMs,\n timestamp: now(),\n })\n } catch (err) {\n console.warn(\n `[theokit:trackAgentTools] onToolEnd hook crashed (swallowed): ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n },\n onToolError(event: ToolHookEvent): void {\n try {\n const start = pending.get(event.callId)\n const startedAt = start?.startedAt ?? now().getTime()\n const durationMs = start ? now().getTime() - startedAt : 0\n pending.delete(event.callId)\n const error = event.error\n let errorMessage = 'unknown error'\n if (error instanceof Error) errorMessage = error.message\n else if (typeof error === 'string') errorMessage = error\n safeRecord({\n kind: 'tool',\n userId: start?.userId ?? userId,\n conversationId: start?.conversationId ?? conversationId,\n toolName: event.name,\n callId: event.callId,\n success: false,\n durationMs,\n errorMessage,\n timestamp: now(),\n })\n } catch (err) {\n console.warn(\n `[theokit:trackAgentTools] onToolError hook crashed (swallowed): ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n },\n }\n}\n\n/**\n * @internal — testing helper. Inspects the size of the pending-starts Map of\n * a factory instance. Not in the public API.\n */\nexport const __internalsForTesting = {\n ORPHAN_TTL_MS,\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server/cost/track-agent-run.ts","../src/server/cost/usage-storage-memory.ts","../src/server/cost/track-agent-tools.ts"],"sourcesContent":["import type { AgentRunRecord } from '../../devtools/shared.js'\n\nimport type { UsageRecord, UsageStorageAdapter } from './cost-types.js'\n\nexport interface TrackAgentRunInput {\n userId: string\n model: string\n tokens: { input: number; output: number }\n costUsd: number\n /** Defaults to `new Date()`. */\n timestamp?: Date\n /** Optional execution status surfaced to devtools (default 'finished'). */\n status?: 'finished' | 'error' | 'aborted'\n}\n\nexport interface TrackAgentRunOptions {\n /** Adapter resolved from `theo.config.ts > cost.storage`. */\n storage: UsageStorageAdapter | undefined\n}\n\n// v1.1 EC-4 MUST FIX — universal dev gate (Vite OR tsup-bundled).\n// Both checks are statically replaceable by bundlers in prod build,\n// so the entire dispatcher import is tree-shaken from the prod bundle.\nconst __IS_DEV = (() => {\n try {\n return (import.meta as { env?: { DEV?: boolean } }).env?.DEV === true\n } catch {\n return process.env.NODE_ENV !== 'production'\n }\n})()\n\n/**\n * Record a single agent run's usage + cost. Companion to the client-side\n * `<CostMeter>` from `@theokit/ui`.\n *\n * EC-14: this function NEVER bubbles errors back to the caller. Adapter\n * failures (network outage, DB down, etc.) are logged via `console.warn`\n * and swallowed. The agent response MUST NOT fail because cost tracking\n * is degraded.\n *\n * No-op when `storage` is undefined (cost tracking unconfigured).\n *\n * In dev mode (Vite OR tsup-built with NODE_ENV != 'production'), fires a\n * `theokit-evolution-ci-and-dx` Phase 3 dispatcher event so the devtools\n * Agents tab can render the run. Prod tree-shakes the entire dispatcher\n * import via the `__IS_DEV` IIFE guard.\n *\n * @see docs/concepts/cost-tracking.md (when implemented in T6.3)\n */\nexport async function trackAgentRun(\n input: TrackAgentRunInput,\n opts: TrackAgentRunOptions,\n): Promise<void> {\n // Dev-only: surface run to devtools Agents tab (T3.1)\n if (__IS_DEV) {\n try {\n const mod = (await import('../../devtools/dispatcher.js')) as {\n dispatcher: { onAgentRun: (r: AgentRunRecord) => void }\n }\n mod.dispatcher.onAgentRun({\n // eslint-disable-next-line sonarjs/pseudo-random -- non-secret correlation id\n id: `run-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`,\n timestamp: (input.timestamp ?? new Date()).getTime(),\n userId: input.userId,\n model: input.model,\n tokensInput: input.tokens.input,\n tokensOutput: input.tokens.output,\n costUsd: input.costUsd,\n status: input.status ?? 'finished',\n })\n } catch {\n // devtools dispatcher missing in some prod-like bundle — silently skip\n }\n }\n\n if (!opts.storage) return\n const record: UsageRecord = {\n userId: input.userId,\n model: input.model,\n tokens: input.tokens,\n costUsd: input.costUsd,\n timestamp: input.timestamp ?? new Date(),\n }\n try {\n await opts.storage.record(record)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n\n console.warn(\n `[theokit:cost] usage storage record failed: ${message}. ` +\n '(Response is unaffected — cost tracking degraded.)',\n )\n }\n}\n","import type { StorageAdapter } from '../storage/storage-types.js'\n\nimport type {\n ToolUsageRecord,\n UsageQuery,\n UsageRecord,\n UsageResult,\n UsageStorageAdapter,\n} from './cost-types.js'\n\n/**\n * In-memory usage storage for dev/tests. Unbounded — production\n * deployments MUST plug in a durable adapter (Postgres/Redis recipes\n * land with R0.6.7; documented in EC-114).\n *\n * Phase 5 — Production-Readiness #4: accepts both `UsageRecord` (LLM call,\n * kind='llm' or omitted) AND `ToolUsageRecord` (tool call, kind='tool').\n * `getUsage` only sums LLM records (tools have no token/cost dimension).\n *\n * EC-9 (backward compat): input without `kind` is normalized to `kind:'llm'`\n * — adapters from older versions stay working.\n *\n * Concurrency: Node's single-threaded event loop guarantees that\n * `Array.push` is atomic, so concurrent record() calls cannot lose data.\n */\nexport class InMemoryUsageStorage implements UsageStorageAdapter, StorageAdapter {\n readonly name = 'memory'\n readonly #records: (UsageRecord | ToolUsageRecord)[] = []\n\n /**\n * T2.3 — `StorageAdapter` lifecycle hook (ADR-0007 D6). In-memory storage\n * has no real cleanup to perform — this is intentionally a noop so the\n * adapter can be registered with `StorageManager.register()` and\n * participate in graceful shutdown without changing behavior.\n *\n * Does NOT clear stored records (dispose ≠ reset). For test reset use\n * a fresh instance.\n */\n dispose(): Promise<void> {\n // Intentional noop — see jsdoc above\n return Promise.resolve()\n }\n\n async record(input: UsageRecord | ToolUsageRecord): Promise<void> {\n // EC-9: normalize legacy input (no `kind` field) to kind:'llm'.\n const normalized: UsageRecord | ToolUsageRecord =\n 'kind' in input && input.kind === 'tool'\n ? { ...input }\n : ({ ...input, kind: 'llm' as const } satisfies UsageRecord)\n this.#records.push(normalized)\n return Promise.resolve()\n }\n\n async getUsage(query: UsageQuery): Promise<UsageResult> {\n const from = query.period.from.getTime()\n const to = query.period.to.getTime()\n let totalTokens = 0\n let totalCostUsd = 0\n let runs = 0\n for (const r of this.#records) {\n if (r.kind === 'tool') continue\n if (r.userId !== query.userId) continue\n const t = r.timestamp.getTime()\n if (t < from || t > to) continue\n totalTokens += r.tokens.input + r.tokens.output\n totalCostUsd += r.costUsd\n runs += 1\n }\n return Promise.resolve({ totalTokens, totalCostUsd, runs })\n }\n\n /** @internal — test helper to inspect raw record stream */\n __getRecords(): readonly (UsageRecord | ToolUsageRecord)[] {\n return this.#records\n }\n}\n","import type { ToolUsageRecord, UsageStorageAdapter } from './cost-types.js'\n\n/**\n * Phase 5 — Production-Readiness #4: tool lifecycle hooks for cost tracking.\n *\n * The SDK calls `onToolStart` → handler runs → `onToolEnd` (success) or\n * `onToolError` (throw). This factory returns the three callbacks ready to\n * attach to `Agent.create({ tools, onToolStart, onToolEnd, onToolError })`.\n *\n * Each successful tool call writes a `ToolUsageRecord{success:true}`; each\n * failure writes `ToolUsageRecord{success:false}` with `errorMessage` from\n * the thrown Error.\n *\n * Invariants:\n * - `onToolStart` records nothing yet (no duration available). Only stashes\n * a `startedAt` timestamp by `callId` in a per-factory Map.\n * - `onToolEnd` reads the stashed timestamp, computes `durationMs`, writes\n * a successful record. Removes the Map entry.\n * - `onToolError` analog with `success:false`.\n * - Hook throws are SWALLOWED to stderr — observation hooks must not crash\n * the run (per the v1.1.0 release contract).\n * - EC-8 (SHOULD TEST) — orphan starts (no matching End/Error) are pruned\n * on every onToolStart if older than 5 minutes. Bounded memory.\n * - EC-16 (DOCUMENT) — callId uniqueness is SDK contract; duplicate Start\n * for the same callId uses last-write-wins.\n */\n\nconst ORPHAN_TTL_MS = 5 * 60 * 1000\n\ninterface PendingStart {\n startedAt: number\n userId: string\n conversationId: string\n}\n\nexport interface TrackAgentToolsOptions {\n storage: UsageStorageAdapter\n /** Defaults to () => new Date() — overridable for deterministic testing. */\n now?: () => Date\n /** Identifier for the user — passed through to ToolUsageRecord.userId. */\n userId?: string\n /** Identifier for the conversation — passed through to ToolUsageRecord.conversationId. */\n conversationId?: string\n}\n\nexport interface ToolHookEvent {\n callId: string\n name: string\n // SDK shape — we only read these structurally\n [key: string]: unknown\n}\n\nexport interface TrackAgentToolsHooks {\n onToolStart: (event: ToolHookEvent) => void\n onToolEnd: (event: ToolHookEvent) => void\n onToolError: (event: ToolHookEvent) => void\n}\n\n/**\n * Create the three tool-lifecycle callbacks. Hand the returned object's\n * methods to `Agent.create({ tools, onToolStart, onToolEnd, onToolError })`.\n */\nexport function trackAgentTools(opts: TrackAgentToolsOptions): TrackAgentToolsHooks {\n const pending = new Map<string, PendingStart>()\n const now = opts.now ?? ((): Date => new Date())\n const userId = opts.userId ?? 'anonymous'\n const conversationId = opts.conversationId ?? 'unknown'\n\n function pruneOrphans(): void {\n const cutoff = now().getTime() - ORPHAN_TTL_MS\n for (const [id, p] of pending) {\n if (p.startedAt < cutoff) pending.delete(id)\n }\n }\n\n function safeRecord(record: ToolUsageRecord): void {\n Promise.resolve(opts.storage.record(record)).catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(`[theokit:trackAgentTools] storage.record failed: ${msg}`)\n })\n }\n\n return {\n onToolStart(event: ToolHookEvent): void {\n try {\n pruneOrphans()\n pending.set(event.callId, {\n startedAt: now().getTime(),\n userId,\n conversationId,\n })\n } catch (err) {\n console.warn(\n `[theokit:trackAgentTools] onToolStart hook crashed (swallowed): ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n },\n onToolEnd(event: ToolHookEvent): void {\n try {\n const start = pending.get(event.callId)\n const startedAt = start?.startedAt ?? now().getTime()\n const durationMs = start ? now().getTime() - startedAt : 0\n pending.delete(event.callId)\n if (!start) {\n console.warn(\n `[theokit:trackAgentTools] orphan onToolEnd for callId=${event.callId}; durationMs:0 recorded`,\n )\n }\n safeRecord({\n kind: 'tool',\n userId: start?.userId ?? userId,\n conversationId: start?.conversationId ?? conversationId,\n toolName: event.name,\n callId: event.callId,\n success: true,\n durationMs,\n timestamp: now(),\n })\n } catch (err) {\n console.warn(\n `[theokit:trackAgentTools] onToolEnd hook crashed (swallowed): ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n },\n onToolError(event: ToolHookEvent): void {\n try {\n const start = pending.get(event.callId)\n const startedAt = start?.startedAt ?? now().getTime()\n const durationMs = start ? now().getTime() - startedAt : 0\n pending.delete(event.callId)\n const error = event.error\n let errorMessage = 'unknown error'\n if (error instanceof Error) errorMessage = error.message\n else if (typeof error === 'string') errorMessage = error\n safeRecord({\n kind: 'tool',\n userId: start?.userId ?? userId,\n conversationId: start?.conversationId ?? conversationId,\n toolName: event.name,\n callId: event.callId,\n success: false,\n durationMs,\n errorMessage,\n timestamp: now(),\n })\n } catch (err) {\n console.warn(\n `[theokit:trackAgentTools] onToolError hook crashed (swallowed): ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n },\n }\n}\n\n/**\n * @internal — testing helper. Inspects the size of the pending-starts Map of\n * a factory instance. Not in the public API.\n */\nexport const __internalsForTesting = {\n ORPHAN_TTL_MS,\n}\n"],"mappings":"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","names":[]}
|
|
@@ -45,6 +45,7 @@ function scanServerActionsEnriched(serverDir) {
|
|
|
45
45
|
walkSourceFiles(actionsDir, { extensions: ACTION_EXTENSIONS }, (absPath) => {
|
|
46
46
|
if (TEST_FILE_RE.test(absPath)) return;
|
|
47
47
|
const rel = relative(actionsDir, absPath).replace(/\\/g, "/");
|
|
48
|
+
if (rel.startsWith("schemas/")) return;
|
|
48
49
|
const name = rel.slice(0, -extname(rel).length);
|
|
49
50
|
const basename = name.includes("/") ? name.split("/").pop() ?? name : name;
|
|
50
51
|
if (RESERVED_NAMES.has(basename)) {
|
|
@@ -59,6 +60,7 @@ function scanServerActionsEnriched(serverDir) {
|
|
|
59
60
|
const accept = /\baccept\s*:\s*['"]form['"]/.test(stripped) ? "form" : "json";
|
|
60
61
|
const hasInput = /\binput\s*:\s*z\./.test(stripped) || /\binput\s*:\s*\w+\(/.test(stripped);
|
|
61
62
|
const exportNames = extractActionExportNames(stripped);
|
|
63
|
+
const schemaFilePath = name.includes("/") ? void 0 : detectSchemaFile(actionsDir, basename);
|
|
62
64
|
for (const exportName of exportNames) {
|
|
63
65
|
const proxyKey = exportName === "default" ? name : exportName;
|
|
64
66
|
if (seenNames.has(proxyKey)) {
|
|
@@ -69,13 +71,17 @@ function scanServerActionsEnriched(serverDir) {
|
|
|
69
71
|
);
|
|
70
72
|
}
|
|
71
73
|
seenNames.add(proxyKey);
|
|
72
|
-
|
|
74
|
+
const entry = {
|
|
73
75
|
name: proxyKey,
|
|
74
76
|
filePath: absPath,
|
|
75
77
|
urlPath: `/api/__actions/${name}/${exportName}`,
|
|
76
78
|
accept,
|
|
77
79
|
hasInput
|
|
78
|
-
}
|
|
80
|
+
};
|
|
81
|
+
if (schemaFilePath !== void 0) {
|
|
82
|
+
entry.schemaFilePath = schemaFilePath;
|
|
83
|
+
}
|
|
84
|
+
entries.push(entry);
|
|
79
85
|
}
|
|
80
86
|
});
|
|
81
87
|
for (const entry of entries) {
|
|
@@ -110,6 +116,15 @@ function extractActionExportNames(stripped) {
|
|
|
110
116
|
if (names.size === 0) names.add("default");
|
|
111
117
|
return [...names];
|
|
112
118
|
}
|
|
119
|
+
function detectSchemaFile(actionsDir, basename) {
|
|
120
|
+
const schemasDir = join(actionsDir, "schemas");
|
|
121
|
+
if (!existsSync(schemasDir)) return void 0;
|
|
122
|
+
for (const ext of [".ts", ".tsx", ".js", ".jsx"]) {
|
|
123
|
+
const candidate = join(schemasDir, `${basename}${ext}`);
|
|
124
|
+
if (existsSync(candidate)) return candidate;
|
|
125
|
+
}
|
|
126
|
+
return void 0;
|
|
127
|
+
}
|
|
113
128
|
function stripComments(source) {
|
|
114
129
|
let out = "";
|
|
115
130
|
let i = 0;
|
|
@@ -137,4 +152,4 @@ export {
|
|
|
137
152
|
scanServerActions,
|
|
138
153
|
scanServerActionsEnriched
|
|
139
154
|
};
|
|
140
|
-
//# sourceMappingURL=chunk-
|
|
155
|
+
//# sourceMappingURL=chunk-7TOMTMHT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/scan/action-scan.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/actions/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport { extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nexport interface ActionNode {\n filePath: string\n actionPath: string\n}\n\n/**\n * Enriched manifest entry per plan g3-server-actions-and-useaction v1.2\n * § Phase 1 / T1.4 + ADR D4. Consumed by virtual module `@theo/actions`\n * (T3.1) and G4 devtools \"Actions\" tab (T5.1).\n */\nexport interface ActionManifestEntry {\n name: string\n filePath: string\n urlPath: string\n accept: 'form' | 'json'\n hasInput: boolean\n /**\n * P#4 plugin-forms shared-schema convention (per plan p4-plugin-forms v1.1 T1.1).\n * When present, points to an isomorphic schema file at\n * `<serverDir>/actions/schemas/<basename>.ts` exporting `export const schema = z.object(...)`.\n * Virtual module emits `import {schema} from '<schemaFilePath>'` + attaches as\n * `actions.X.__zodSchema` so client-side <TheoForm> can drive zodResolver.\n * Undefined when convention not followed (graceful degrade — TheoForm still\n * works via explicit `schema={...}` prop escape hatch).\n */\n schemaFilePath?: string\n}\n\n/**\n * EC-2: structured error for scan-time defects (name collision, reserved\n * identifier, etc.). Throw at scan time to fail loud — silent shadowing is\n * a security/correctness footgun.\n */\nexport class ActionScanError extends Error {\n readonly code: 'NAME_COLLISION' | 'RESERVED_NAME'\n readonly conflictingPaths: readonly string[]\n\n constructor(\n code: 'NAME_COLLISION' | 'RESERVED_NAME',\n message: string,\n conflictingPaths: readonly string[],\n ) {\n super(message)\n this.code = code\n this.conflictingPaths = conflictingPaths\n }\n}\n\nconst ACTION_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\nconst TEST_FILE_RE = /\\.(test|spec)\\.(ts|tsx|js|jsx)$/\nconst RESERVED_NAMES = new Set(['index', 'constructor', '__proto__', 'prototype', 'hasOwnProperty'])\n\n/**\n * Backward-compatible: original simple-shape scanner used by existing\n * consumers. Preserved verbatim; new consumers should call\n * `scanServerActionsEnriched`.\n */\nexport function scanServerActions(serverDir: string): ActionNode[] {\n const actionsDir = join(serverDir, 'actions')\n if (!existsSync(actionsDir) || !statSync(actionsDir).isDirectory()) {\n return []\n }\n\n const results: ActionNode[] = []\n walkSourceFiles(actionsDir, { extensions: ACTION_EXTENSIONS }, (absPath) => {\n if (TEST_FILE_RE.test(absPath)) return\n let rel = relative(actionsDir, absPath)\n rel = rel.replace(/\\\\/g, '/')\n rel = rel.slice(0, -extname(rel).length)\n results.push({\n filePath: absPath,\n actionPath: rel,\n })\n })\n return results\n}\n\n/**\n * Enriched scan: light AST detection of `accept: 'form'|'json'` + `input:`\n * presence via regex-after-comment-stripping (EC-9). Throws ActionScanError\n * on file/dir name collision (EC-2) or reserved JS identifier names.\n *\n * Output `ActionManifestEntry[]` is sorted by `name` for deterministic\n * `.theo/actions-manifest.json` emission.\n */\nexport function scanServerActionsEnriched(serverDir: string): ActionManifestEntry[] {\n const actionsDir = join(serverDir, 'actions')\n if (!existsSync(actionsDir) || !statSync(actionsDir).isDirectory()) {\n return []\n }\n\n const seenNames = new Set<string>()\n const entries: ActionManifestEntry[] = []\n\n // Collect first; collision-check after the full walk.\n walkSourceFiles(actionsDir, { extensions: ACTION_EXTENSIONS }, (absPath) => {\n if (TEST_FILE_RE.test(absPath)) return\n const rel = relative(actionsDir, absPath).replace(/\\\\/g, '/')\n // P#4 plugin-forms — `schemas/` subdir holds isomorphic zod schemas\n // for the shared-schema convention (T1.1). NOT executable actions; skip.\n if (rel.startsWith('schemas/')) return\n const name = rel.slice(0, -extname(rel).length)\n\n const basename = name.includes('/') ? (name.split('/').pop() ?? name) : name\n if (RESERVED_NAMES.has(basename)) {\n throw new ActionScanError(\n 'RESERVED_NAME',\n `Reserved JS identifier \"${basename}\" cannot be an action name (${absPath})`,\n [absPath],\n )\n }\n // File-level collision (one file appearing twice) is impossible via the\n // walker; per-export collision check happens inside the export loop below.\n const source = readFileSync(absPath, 'utf8')\n const stripped = stripComments(source)\n const accept = /\\baccept\\s*:\\s*['\"]form['\"]/.test(stripped) ? 'form' : 'json'\n const hasInput = /\\binput\\s*:\\s*z\\./.test(stripped) || /\\binput\\s*:\\s*\\w+\\(/.test(stripped)\n\n // T7.1 wire fix — extract exports so the urlPath includes the second\n // segment required by action-middleware (`/api/__actions/<file>/<export>`).\n // Each named action export becomes its own manifest entry.\n // Proxy key on the EXPORT name (so consumers write `actions.saveMemory(input)`).\n // URL keeps the runtime 2-segment shape `/api/__actions/<file>/<export>`\n // expected by action-middleware. Cross-file export collisions become a\n // scan error to surface the ambiguity early.\n const exportNames = extractActionExportNames(stripped)\n // P#4 plugin-forms shared-schema convention: check for\n // `<actionsDir>/schemas/<basename>.ts` (or .tsx/.js/.jsx).\n // Skip when actions live in subdirs (`schemas/` is flat by convention).\n const schemaFilePath = name.includes('/') ? undefined : detectSchemaFile(actionsDir, basename)\n for (const exportName of exportNames) {\n const proxyKey = exportName === 'default' ? name : exportName\n if (seenNames.has(proxyKey)) {\n throw new ActionScanError(\n 'NAME_COLLISION',\n `Duplicate action proxy key \"${proxyKey}\" (two files export the same name)`,\n [absPath],\n )\n }\n seenNames.add(proxyKey)\n const entry: ActionManifestEntry = {\n name: proxyKey,\n filePath: absPath,\n urlPath: `/api/__actions/${name}/${exportName}`,\n accept,\n hasInput,\n }\n if (schemaFilePath !== undefined) {\n entry.schemaFilePath = schemaFilePath\n }\n entries.push(entry)\n }\n })\n\n // EC-2: detect file vs dir collisions (e.g., foo.ts AND foo/bar.ts).\n // After walk completes, any name that has children prefixed `name/` triggers collision.\n for (const entry of entries) {\n const childPrefix = `${entry.name}/`\n const conflictingChild = entries.find((other) => other.name.startsWith(childPrefix))\n if (conflictingChild) {\n throw new ActionScanError(\n 'NAME_COLLISION',\n `Action \"${entry.name}\" conflicts with directory of same name containing \"${conflictingChild.name}\"`,\n [entry.filePath, conflictingChild.filePath],\n )\n }\n }\n\n entries.sort((a, b) => {\n if (a.name < b.name) return -1\n if (a.name > b.name) return 1\n return 0\n })\n return entries\n}\n\n/**\n * Strip JavaScript line + block comments from source. Simple state machine\n * (does not parse strings — false positives if a comment marker appears\n * inside a string literal, but that's an acceptable trade-off for v1 vs\n * full AST parse).\n */\n/**\n * Extract action export names via regex over comment-stripped source.\n * Matches `export const <name> = defineAction(...)`, `export default\n * defineAction(...)`, and `export function <name>(...)` forms. Returns\n * `['default']` when no named action exports are found (best-effort fallback\n * for default-export shapes the regex misses).\n */\nfunction extractActionExportNames(stripped: string): string[] {\n const names = new Set<string>()\n const namedRe = /\\bexport\\s+(?:const|let|var|function\\*?)\\s+([a-zA-Z_$][\\w$]*)\\s*[=(]/g\n let m: RegExpExecArray | null\n while ((m = namedRe.exec(stripped)) !== null) {\n const name = m[1]\n if (typeof name === 'string' && name.length > 0) names.add(name)\n }\n if (/\\bexport\\s+default\\s+defineAction\\b/.test(stripped)) {\n names.add('default')\n }\n if (names.size === 0) names.add('default')\n return [...names]\n}\n\n/**\n * P#4 plugin-forms shared-schema convention helper (per plan p4-plugin-forms v1.1 T1.1).\n * Returns the resolved path to `<actionsDir>/schemas/<basename>.<ext>` if it exists.\n * Tries `.ts`, `.tsx`, `.js`, `.jsx` in that order. Returns undefined when no match.\n */\nfunction detectSchemaFile(actionsDir: string, basename: string): string | undefined {\n const schemasDir = join(actionsDir, 'schemas')\n if (!existsSync(schemasDir)) return undefined\n for (const ext of ['.ts', '.tsx', '.js', '.jsx']) {\n const candidate = join(schemasDir, `${basename}${ext}`)\n if (existsSync(candidate)) return candidate\n }\n return undefined\n}\n\nfunction stripComments(source: string): string {\n let out = ''\n let i = 0\n while (i < source.length) {\n const ch = source[i]\n const next = source[i + 1]\n if (ch === '/' && next === '/') {\n // Line comment: skip until newline\n while (i < source.length && source[i] !== '\\n') i++\n continue\n }\n if (ch === '/' && next === '*') {\n // Block comment: skip until */\n i += 2\n while (i < source.length - 1 && !(source[i] === '*' && source[i + 1] === '/')) i++\n i += 2\n continue\n }\n out += ch\n i++\n }\n return out\n}\n"],"mappings":"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","names":[]}
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
compilePattern,
|
|
5
5
|
scanServerRoutes,
|
|
6
6
|
scanWebSocketRoutes
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-SNDZQ64K.js";
|
|
8
8
|
import {
|
|
9
9
|
scanServerActions
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5OFPQK6N.js";
|
|
11
11
|
|
|
12
12
|
// src/server/scan/manifest.ts
|
|
13
13
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
@@ -72,4 +72,4 @@ export {
|
|
|
72
72
|
writeManifest,
|
|
73
73
|
loadManifest
|
|
74
74
|
};
|
|
75
|
-
//# sourceMappingURL=chunk-
|
|
75
|
+
//# sourceMappingURL=chunk-BE2LKDI7.js.map
|