Dax Raad 8 месяцев назад
Родитель
Сommit
fa1266263d

+ 23 - 5
bun.lock

@@ -91,16 +91,18 @@
   },
   "catalog": {
     "@types/node": "22.13.9",
-    "ai": "5.0.0-alpha.7",
+    "ai": "4.3.16",
     "typescript": "5.8.2",
     "zod": "3.24.2",
   },
   "packages": {
-    "@ai-sdk/gateway": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-alpha.7", "@ai-sdk/provider-utils": "3.0.0-alpha.7" }, "peerDependencies": { "zod": "^3.24.0" } }, "sha512-gz1V165eiJnQIexfLyKm11vimrmQ3zdcJhPpjeLFmDU9wrvZwLuklfZ0WgfYSb+EjiP1cKypwt6JSGvWkfKIAQ=="],
+    "@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
 
-    "@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-lhdrARU3SSmt5p/GNNK7VhazvZpKSCIOjpHUfX7f5jIhVGi/vvlxP1rD6Go57nn1MtuGKNqL04AebSRFDQsQbw=="],
+    "@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
 
-    "@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-alpha.7", "@standard-schema/spec": "^1.0.0", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-AYkT3jskmo7Lwzijo/yHKD1jC+UZizsROO8ULTg9aJZUwR4ABZzAxh4NxDIEy4TWRfBGufp+/9ICHAn6pkU71w=="],
+    "@ai-sdk/react": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/ui-utils": "1.2.11", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["zod"] }, "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g=="],
+
+    "@ai-sdk/ui-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w=="],
 
     "@ampproject/remapping": ["@ampproject/[email protected]", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
 
@@ -430,6 +432,8 @@
 
     "@types/debug": ["@types/[email protected]", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
 
+    "@types/diff-match-patch": ["@types/[email protected]", "", {}, "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg=="],
+
     "@types/estree": ["@types/[email protected]", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
 
     "@types/estree-jsx": ["@types/[email protected]", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
@@ -474,7 +478,7 @@
 
     "acorn-walk": ["[email protected]", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="],
 
-    "ai": ["ai@5.0.0-alpha.7", "", { "dependencies": { "@ai-sdk/gateway": "1.0.0-alpha.7", "@ai-sdk/provider": "2.0.0-alpha.7", "@ai-sdk/provider-utils": "3.0.0-alpha.7", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-ShCk3frIMdVtK9knvWKiFS7N6Vwnf8mLMv670+T//W9oqfoetSVPBhTF6Dy+oDM/bjVSsBf1BuYImLDvHICOIQ=="],
+    "ai": ["ai@4.3.16", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/react": "1.2.12", "@ai-sdk/ui-utils": "1.2.11", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["react"] }, "sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g=="],
 
     "ansi-align": ["[email protected]", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
 
@@ -686,6 +690,8 @@
 
     "diff": ["[email protected]", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="],
 
+    "diff-match-patch": ["[email protected]", "", {}, "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="],
+
     "direction": ["[email protected]", "", { "bin": { "direction": "cli.js" } }, "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA=="],
 
     "dlv": ["[email protected]", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
@@ -970,6 +976,8 @@
 
     "json5": ["[email protected]", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
 
+    "jsondiffpatch": ["[email protected]", "", { "dependencies": { "@types/diff-match-patch": "^1.0.36", "chalk": "^5.3.0", "diff-match-patch": "^1.0.5" }, "bin": { "jsondiffpatch": "bin/jsondiffpatch.js" } }, "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ=="],
+
     "kleur": ["[email protected]", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
 
     "klona": ["[email protected]", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="],
@@ -1274,6 +1282,8 @@
 
     "rc": ["[email protected]", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
 
+    "react": ["[email protected]", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
+
     "readable-stream": ["[email protected]", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
 
     "readdirp": ["[email protected]", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
@@ -1354,6 +1364,8 @@
 
     "sax": ["[email protected]", "", {}, "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA=="],
 
+    "secure-json-parse": ["[email protected]", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="],
+
     "semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
 
     "send": ["[email protected]", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
@@ -1454,6 +1466,8 @@
 
     "supports-color": ["[email protected]", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
 
+    "swr": ["[email protected]", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A=="],
+
     "tar-fs": ["[email protected]", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA=="],
 
     "tar-stream": ["[email protected]", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
@@ -1462,6 +1476,8 @@
 
     "thread-stream": ["[email protected]", "", { "dependencies": { "real-require": "^0.1.0" } }, "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA=="],
 
+    "throttleit": ["[email protected]", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="],
+
     "tiny-inflate": ["[email protected]", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
 
     "tinyexec": ["[email protected]", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
@@ -1546,6 +1562,8 @@
 
     "url": ["[email protected]", "", { "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" } }, "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ=="],
 
+    "use-sync-external-store": ["[email protected]", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="],
+
     "util": ["[email protected]", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="],
 
     "util-deprecate": ["[email protected]", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],

+ 1 - 1
package.json

@@ -15,7 +15,7 @@
       "typescript": "5.8.2",
       "@types/node": "22.13.9",
       "zod": "3.24.2",
-      "ai": "5.0.0-alpha.7"
+      "ai": "4.3.16"
     }
   },
   "devDependencies": {

+ 19 - 10
packages/opencode/src/bun/index.ts

@@ -44,15 +44,24 @@ export namespace BunProc {
     }),
   )
   export async function install(pkg: string, version = "latest") {
-    const dir = path.join(Global.Path.cache, `node_modules`, pkg)
-    if (!(await Bun.file(path.join(dir, "package.json")).exists())) {
-      log.info("installing", { pkg })
-      await BunProc.run(["add", `${pkg}@${version}`], {
-        cwd: Global.Path.cache,
-      }).catch(() => {
-        throw new InstallFailedError({ pkg, version })
-      })
-    }
-    return dir
+    const mod = path.join(Global.Path.cache, "node_modules", pkg)
+    const pkgjson = Bun.file(path.join(Global.Path.cache, "package.json"))
+    const parsed = await pkgjson.json().catch(() => ({
+      dependencies: {},
+    }))
+    if (parsed.dependencies[pkg] === version) return mod
+    parsed.dependencies[pkg] = version
+    await Bun.write(pkgjson, JSON.stringify(parsed, null, 2))
+    await BunProc.run(["install"], {
+      cwd: Global.Path.cache,
+    }).catch((e) => {
+      new InstallFailedError(
+        { pkg, version },
+        {
+          cause: e,
+        },
+      )
+    })
+    return mod
   }
 }

+ 1 - 1
packages/opencode/src/provider/models.ts

@@ -86,7 +86,7 @@ export namespace ModelsDev {
   export async function pkg(providerID: string): Promise<[string, string]> {
     const packages = await aisdk()
     const match = packages[`@ai-sdk/${providerID}`]
-    if (match) return [match.package.name, "alpha"]
+    if (match) return [match.package.name, "latest"]
     return [providerID, "latest"]
   }
 }

+ 205 - 138
packages/opencode/src/session/index.ts

@@ -4,15 +4,15 @@ import { Identifier } from "../id/id"
 import { Storage } from "../storage/storage"
 import { Log } from "../util/log"
 import {
-  convertToModelMessages,
   generateText,
   LoadAPIKeyError,
-  stepCountIs,
   streamText,
   tool,
   type Tool as AITool,
   type LanguageModelUsage,
-  type UIMessage,
+  type CoreMessage,
+  type UserContent,
+  type AssistantContent,
 } from "ai"
 import { z, ZodSchema } from "zod"
 import { Decimal } from "decimal.js"
@@ -28,6 +28,7 @@ import { NamedError } from "../util/error"
 import type { Tool } from "../tool/tool"
 import { SystemPrompt } from "./system"
 import { Flag } from "../flag/flag"
+import type { ModelsDev } from "../provider/models"
 
 export namespace Session {
   const log = Log.create({ service: "session" })
@@ -227,34 +228,28 @@ export namespace Session {
     const session = await get(input.sessionID)
     if (msgs.length === 0 && !session.parentID) {
       generateText({
-        maxOutputTokens: 20,
-        messages: convertToModelMessages([
+        maxTokens: input.providerID === "google" ? 1024 : 20,
+        messages: [
           ...SystemPrompt.title(input.providerID).map(
-            (x): UIMessage => ({
-              id: Identifier.ascending("message"),
+            (x): CoreMessage => ({
               role: "system",
-              parts: [
-                {
-                  type: "text",
-                  text: x,
-                },
-              ],
+              content: x,
             }),
           ),
           {
             role: "user",
-            parts: input.parts,
+            content: toUserContent(input.parts),
           },
-        ]),
-        temperature: 0,
+        ],
         model: model.language,
       })
         .then((result) => {
-          return Session.update(input.sessionID, (draft) => {
-            draft.title = result.text
-          })
+          if (result.text)
+            return Session.update(input.sessionID, (draft) => {
+              draft.title = result.text
+            })
         })
-        .catch(() => {})
+        .catch((e) => {})
     }
     const msg: Message.Info = {
       role: "user",
@@ -400,90 +395,9 @@ export namespace Session {
         }
         text = undefined
       },
-      async onChunk(input) {
-        const value = input.chunk
-        l.info("part", {
-          type: value.type,
-        })
-        switch (value.type) {
-          case "text":
-            if (!text) {
-              text = value
-              next.parts.push(value)
-              break
-            } else text.text += value.text
-            break
-
-          case "tool-call": {
-            const [match] = next.parts.flatMap((p) =>
-              p.type === "tool-invocation" &&
-              p.toolInvocation.toolCallId === value.toolCallId
-                ? [p]
-                : [],
-            )
-            if (!match) break
-            match.toolInvocation.args = value.args
-            match.toolInvocation.state = "call"
-            Bus.publish(Message.Event.PartUpdated, {
-              part: match,
-              messageID: next.id,
-              sessionID: next.metadata.sessionID,
-            })
-            break
-          }
-
-          case "tool-call-streaming-start":
-            next.parts.push({
-              type: "tool-invocation",
-              toolInvocation: {
-                state: "partial-call",
-                toolName: value.toolName,
-                toolCallId: value.toolCallId,
-                args: {},
-              },
-            })
-            Bus.publish(Message.Event.PartUpdated, {
-              part: next.parts[next.parts.length - 1],
-              messageID: next.id,
-              sessionID: next.metadata.sessionID,
-            })
-            break
-
-          case "tool-call-delta":
-            break
-
-          case "tool-result":
-            const match = next.parts.find(
-              (p) =>
-                p.type === "tool-invocation" &&
-                p.toolInvocation.toolCallId === value.toolCallId,
-            )
-            if (match && match.type === "tool-invocation") {
-              match.toolInvocation = {
-                args: value.args,
-                toolCallId: value.toolCallId,
-                toolName: value.toolName,
-                state: "result",
-                result: value.result as string,
-              }
-              Bus.publish(Message.Event.PartUpdated, {
-                part: match,
-                messageID: next.id,
-                sessionID: next.metadata.sessionID,
-              })
-            }
-            break
-
-          default:
-            l.info("unhandled", {
-              type: value.type,
-            })
-        }
-        await updateMessage(next)
-      },
       async onFinish(input) {
         const assistant = next.metadata!.assistant!
-        const usage = getUsage(input.totalUsage, model.info)
+        const usage = getUsage(input.usage, model.info)
         assistant.cost = usage.cost
         await updateMessage(next)
       },
@@ -515,31 +429,44 @@ export namespace Session {
           error: next.metadata.error,
         })
       },
-      async prepareStep(step) {
-        next.parts.push({
-          type: "step-start",
-        })
-        await updateMessage(next)
-        return step
-      },
+      // async prepareStep(step) {
+      //   next.parts.push({
+      //     type: "step-start",
+      //   })
+      //   await updateMessage(next)
+      //   return step
+      // },
       toolCallStreaming: true,
       abortSignal: abort.signal,
-      stopWhen: stepCountIs(1000),
-      messages: convertToModelMessages([
+      maxSteps: 1000,
+      messages: [
         ...system.map(
-          (x): UIMessage => ({
-            id: Identifier.ascending("message"),
+          (x): CoreMessage => ({
             role: "system",
-            parts: [
-              {
-                type: "text",
-                text: x,
-              },
-            ],
+            content: x,
           }),
         ),
-        ...msgs,
-      ]),
+        ...msgs.flatMap((msg): CoreMessage[] => {
+          switch (msg.role) {
+            case "user":
+              return [
+                {
+                  role: "user",
+                  content: toUserContent(msg.parts),
+                },
+              ]
+            case "assistant":
+              return [
+                {
+                  role: "assistant",
+                  content: toAssistantContent(msg.parts),
+                },
+              ]
+            default:
+              return []
+          }
+        }),
+      ],
       temperature: model.info.id === "codex-mini-latest" ? undefined : 0,
       tools: {
         ...(await MCP.tools()),
@@ -547,6 +474,101 @@ export namespace Session {
       },
       model: model.language,
     })
+    for await (const value of result.fullStream) {
+      l.info("part", {
+        type: value.type,
+      })
+      switch (value.type) {
+        case "step-start":
+          next.parts.push({
+            type: "step-start",
+          })
+          break
+        case "text-delta":
+          if (!text) {
+            text = {
+              type: "text",
+              text: value.textDelta,
+            }
+            next.parts.push(text)
+            break
+          } else text.text += value.textDelta
+          break
+
+        case "tool-call": {
+          const [match] = next.parts.flatMap((p) =>
+            p.type === "tool-invocation" &&
+            p.toolInvocation.toolCallId === value.toolCallId
+              ? [p]
+              : [],
+          )
+          if (!match) break
+          match.toolInvocation.args = value.args
+          match.toolInvocation.state = "call"
+          Bus.publish(Message.Event.PartUpdated, {
+            part: match,
+            messageID: next.id,
+            sessionID: next.metadata.sessionID,
+          })
+          break
+        }
+
+        case "tool-call-streaming-start":
+          next.parts.push({
+            type: "tool-invocation",
+            toolInvocation: {
+              state: "partial-call",
+              toolName: value.toolName,
+              toolCallId: value.toolCallId,
+              args: {},
+            },
+          })
+          Bus.publish(Message.Event.PartUpdated, {
+            part: next.parts[next.parts.length - 1],
+            messageID: next.id,
+            sessionID: next.metadata.sessionID,
+          })
+          break
+
+        case "tool-call-delta":
+          break
+
+        // for some reason ai sdk claims to not send this part but it does
+        // @ts-expect-error
+        case "tool-result":
+          const match = next.parts.find(
+            (p) =>
+              p.type === "tool-invocation" &&
+              // @ts-expect-error
+              p.toolInvocation.toolCallId === value.toolCallId,
+          )
+          if (match && match.type === "tool-invocation") {
+            match.toolInvocation = {
+              // @ts-expect-error
+              args: value.args,
+              // @ts-expect-error
+              toolCallId: value.toolCallId,
+              // @ts-expect-error
+              toolName: value.toolName,
+              state: "result",
+              // @ts-expect-error
+              result: value.result as string,
+            }
+            Bus.publish(Message.Event.PartUpdated, {
+              part: match,
+              messageID: next.id,
+              sessionID: next.metadata.sessionID,
+            })
+          }
+          break
+
+        default:
+          l.info("unhandled", {
+            type: value.type,
+          })
+      }
+      await updateMessage(next)
+    }
     await result.consumeStream({
       onError: (err) => {
         log.error("stream error", {
@@ -618,30 +640,23 @@ export namespace Session {
     const result = await generateText({
       abortSignal: abort.signal,
       model: model.language,
-      messages: convertToModelMessages([
+      messages: [
         ...system.map(
-          (x): UIMessage => ({
-            id: Identifier.ascending("message"),
+          (x): CoreMessage => ({
             role: "system",
-            parts: [
-              {
-                type: "text",
-                text: x,
-              },
-            ],
+            content: x,
           }),
         ),
-        ...filtered,
         {
           role: "user",
-          parts: [
+          content: toUserContent([
             {
               type: "text",
               text: "Provide a detailed but concise summary of our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next.",
             },
-          ],
+          ]),
         },
-      ]),
+      ],
     })
     next.parts.push({
       type: "text",
@@ -669,11 +684,11 @@ export namespace Session {
     }
   }
 
-  function getUsage(usage: LanguageModelUsage, model: Provider.Model) {
+  function getUsage(usage: LanguageModelUsage, model: ModelsDev.Model) {
     const tokens = {
-      input: usage.inputTokens ?? 0,
-      output: usage.outputTokens ?? 0,
-      reasoning: usage.reasoningTokens ?? 0,
+      input: usage.promptTokens ?? 0,
+      output: usage.completionTokens ?? 0,
+      reasoning: 0,
     }
     return {
       cost: new Decimal(0)
@@ -710,3 +725,55 @@ export namespace Session {
     await App.initialize()
   }
 }
+
+function toAssistantContent(parts: Message.Part[]): AssistantContent {
+  const result: AssistantContent = []
+  for (const part of parts) {
+    switch (part.type) {
+      case "text":
+        result.push({ type: "text", text: part.text })
+        break
+      case "file":
+        result.push({
+          type: "file",
+          data: new URL(part.url),
+          mimeType: part.mediaType,
+          filename: part.filename,
+        })
+        break
+      case "tool-invocation":
+        result.push({
+          type: "tool-call",
+          args: part.toolInvocation.args,
+          toolName: part.toolInvocation.toolName,
+          toolCallId: part.toolInvocation.toolCallId,
+        })
+        break
+      default:
+        break
+    }
+  }
+  return result
+}
+
+function toUserContent(parts: Message.Part[]): UserContent {
+  const result: UserContent = []
+  for (const part of parts) {
+    switch (part.type) {
+      case "text":
+        return [{ type: "text", text: part.text }]
+      case "file":
+        return [
+          {
+            type: "file",
+            filename: part.filename,
+            data: new URL(part.url),
+            mimeType: part.mediaType,
+          },
+        ]
+      default:
+        return []
+    }
+  }
+  return result
+}