Dax Raad 10 bulan lalu
induk
melakukan
afe741b63e
5 mengubah file dengan 36 tambahan dan 13 penghapusan
  1. 1 0
      js/bun.lock
  2. 1 0
      js/package.json
  3. 1 1
      js/src/app/config.ts
  4. 19 7
      js/src/llm/llm.ts
  5. 14 5
      js/src/session/session.ts

+ 1 - 0
js/bun.lock

@@ -11,6 +11,7 @@
         "ai": "^5.0.0-alpha.4",
         "cac": "^6.7.14",
         "clipanion": "^4.0.0-rc.4",
+        "decimal.js": "^10.5.0",
         "diff": "^8.0.2",
         "env-paths": "^3.0.0",
         "hono": "^4.7.10",

+ 1 - 0
js/package.json

@@ -27,6 +27,7 @@
     "ai": "^5.0.0-alpha.4",
     "cac": "^6.7.14",
     "clipanion": "^4.0.0-rc.4",
+    "decimal.js": "^10.5.0",
     "diff": "^8.0.2",
     "env-paths": "^3.0.0",
     "hono": "^4.7.10",

+ 1 - 1
js/src/app/config.ts

@@ -22,7 +22,7 @@ export namespace Config {
 
   export const Provider = z.object({
     options: z.record(z.string(), z.any()).optional(),
-    models: z.record(z.string(), Model).optional(),
+    models: z.record(z.string(), Model),
   });
   export type Provider = z.output<typeof Provider>;
 

+ 19 - 7
js/src/llm/llm.ts

@@ -24,10 +24,10 @@ export namespace LLM {
         "claude-sonnet-4-20250514": {
           name: "Claude 4 Sonnet",
           cost: {
-            input: 3.0,
-            inputCached: 3.75,
-            output: 15.0,
-            outputCached: 0.3,
+            input: 3.0 / 1_000_000,
+            inputCached: 3.75 / 1_000_000,
+            output: 15.0 / 1_000_000,
+            outputCached: 0.3 / 1_000_000,
           },
           contextWindow: 200000,
           maxTokens: 50000,
@@ -49,6 +49,10 @@ export namespace LLM {
         instance: Provider;
       }
     > = {};
+    const models = new Map<
+      string,
+      { info: Config.Model; instance: LanguageModel }
+    >();
 
     const list = mergeDeep(NATIVE_PROVIDERS, app.config.providers ?? {});
 
@@ -82,7 +86,7 @@ export namespace LLM {
     }
 
     return {
-      models: new Map<string, LanguageModel>(),
+      models,
       providers,
     };
   });
@@ -101,11 +105,19 @@ export namespace LLM {
       providerID,
       modelID,
     });
+    const info = provider.info.models[modelID];
+    if (!info) throw new ModelNotFoundError(modelID);
     try {
       const match = provider.instance.languageModel(modelID);
       log.info("found", { providerID, modelID });
-      s.models.set(key, match);
-      return match;
+      s.models.set(key, {
+        info,
+        instance: match,
+      });
+      return {
+        info,
+        instance: match,
+      };
     } catch (e) {
       if (e instanceof NoSuchModelError) throw new ModelNotFoundError(modelID);
       throw e;

+ 14 - 5
js/src/session/session.ts

@@ -17,6 +17,7 @@ import {
 } from "ai";
 import { z } from "zod";
 import * as tools from "../tool";
+import { Decimal } from "decimal.js";
 
 import PROMPT_ANTHROPIC from "./prompt/anthropic.txt";
 import PROMPT_TITLE from "./prompt/title.txt";
@@ -31,6 +32,7 @@ export namespace Session {
     id: Identifier.schema("session"),
     shareID: z.string().optional(),
     title: z.string(),
+    cost: z.number().optional(),
     tokens: z.object({
       input: z.number(),
       output: z.number(),
@@ -188,7 +190,7 @@ export namespace Session {
             parts: input.parts,
           },
         ]),
-        model,
+        model: model.instance,
       }).then((result) => {
         return Session.update(input.sessionID, (draft) => {
           draft.title = result.text;
@@ -226,16 +228,23 @@ export namespace Session {
     const result = streamText({
       onStepFinish: (step) => {
         update(input.sessionID, (draft) => {
-          draft.tokens.input += step.usage.inputTokens || 0;
-          draft.tokens.output += step.usage.outputTokens || 0;
-          draft.tokens.reasoning += step.usage.reasoningTokens || 0;
+          const input = step.usage.inputTokens ?? 0;
+          const output = step.usage.outputTokens ?? 0;
+          const reasoning = step.usage.reasoningTokens ?? 0;
+          draft.tokens.input += input;
+          draft.tokens.output += output;
+          draft.tokens.reasoning += reasoning;
+          draft.cost = new Decimal(draft.cost ?? 0)
+            .add(new Decimal(input).mul(model.info.cost.input))
+            .add(new Decimal(output).mul(model.info.cost.output))
+            .toNumber();
         });
       },
       stopWhen: stepCountIs(1000),
       messages: convertToModelMessages(msgs),
       temperature: 0,
       tools,
-      model,
+      model: model.instance,
     });
 
     msgs.push(next);