diff --git a/bun.lock b/bun.lock index f2547af4..16521122 100644 --- a/bun.lock +++ b/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/gateway@1.0.0-alpha.7", "", { "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/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="], - "@ai-sdk/provider": ["@ai-sdk/provider@2.0.0-alpha.7", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-lhdrARU3SSmt5p/GNNK7VhazvZpKSCIOjpHUfX7f5jIhVGi/vvlxP1rD6Go57nn1MtuGKNqL04AebSRFDQsQbw=="], + "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "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/provider-utils@3.0.0-alpha.7", "", { "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/react@1.2.12", "", { "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/ui-utils@1.2.11", "", { "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/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], @@ -430,6 +432,8 @@ "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + "@types/diff-match-patch": ["@types/diff-match-patch@1.0.36", "", {}, "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg=="], + "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], @@ -474,7 +478,7 @@ "acorn-walk": ["acorn-walk@8.3.2", "", {}, "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": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], @@ -686,6 +690,8 @@ "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="], + "diff-match-patch": ["diff-match-patch@1.0.5", "", {}, "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="], + "direction": ["direction@2.0.1", "", { "bin": { "direction": "cli.js" } }, "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA=="], "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], @@ -970,6 +976,8 @@ "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + "jsondiffpatch": ["jsondiffpatch@0.6.0", "", { "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": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], "klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="], @@ -1274,6 +1282,8 @@ "rc": ["rc@1.2.8", "", { "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": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], @@ -1354,6 +1364,8 @@ "sax": ["sax@1.2.1", "", {}, "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA=="], + "secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="], + "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "send": ["send@1.2.0", "", { "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": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "swr": ["swr@2.3.3", "", { "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": ["tar-fs@3.0.9", "", { "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": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="], @@ -1462,6 +1476,8 @@ "thread-stream": ["thread-stream@0.15.2", "", { "dependencies": { "real-require": "^0.1.0" } }, "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA=="], + "throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="], + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], @@ -1546,6 +1562,8 @@ "url": ["url@0.10.3", "", { "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" } }, "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ=="], + "use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="], + "util": ["util@0.12.5", "", { "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": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], diff --git a/package.json b/package.json index 0f4dc8c4..f454d7d9 100644 --- a/package.json +++ b/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": { diff --git a/packages/opencode/src/bun/index.ts b/packages/opencode/src/bun/index.ts index fc8e7e83..a003f1c5 100644 --- a/packages/opencode/src/bun/index.ts +++ b/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 } } diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index f6b2653d..334b1191 100644 --- a/packages/opencode/src/provider/models.ts +++ b/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"] } } diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 3cf2a383..43f6720f 100644 --- a/packages/opencode/src/session/index.ts +++ b/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 +}