feat(tui): add v2 terminal interface
This commit is contained in:
parent
0e2dd4ad15
commit
e6f660fecf
73 changed files with 3028 additions and 2035 deletions
|
|
@ -601,9 +601,9 @@ describe("Config", () => {
|
|||
models: {
|
||||
model: {
|
||||
request: {
|
||||
body: { temperature: 0.3, reasoning_effort: "high", service_tier: "priority" },
|
||||
body: { temperature: 0.3, reasoning: { effort: "high" }, service_tier: "priority" },
|
||||
},
|
||||
variants: [{ id: "high", body: { reasoning_effort: "high", reasoning_summary: "auto" } }],
|
||||
variants: [{ id: "high", body: { reasoning: { effort: "high", summary: "auto" } } }],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -42,12 +42,14 @@ describe("ConfigProviderOptionsV1", () => {
|
|||
expect(
|
||||
lowerer.request({
|
||||
reasoningEffort: "high",
|
||||
reasoningSummary: "auto",
|
||||
reasoning: { encryptedContent: true },
|
||||
textVerbosity: "low",
|
||||
text: { outputFormat: "plain" },
|
||||
nestedValue: { camelCase: true },
|
||||
}),
|
||||
).toEqual({
|
||||
reasoning_effort: "high",
|
||||
reasoning: { encrypted_content: true, effort: "high", summary: "auto" },
|
||||
text: { output_format: "plain", verbosity: "low" },
|
||||
nested_value: { camel_case: true },
|
||||
})
|
||||
|
|
@ -138,8 +140,8 @@ describe("ConfigProviderOptionsV1", () => {
|
|||
body: { trace: true },
|
||||
settings: { resourceName: "resource" },
|
||||
})
|
||||
expect(lowerer.request({ reasoningEffort: "high", textVerbosity: "low" })).toEqual({
|
||||
reasoning_effort: "high",
|
||||
expect(lowerer.request({ reasoningEffort: "high", reasoningSummary: "auto", textVerbosity: "low" })).toEqual({
|
||||
reasoning: { effort: "high", summary: "auto" },
|
||||
text: { verbosity: "low" },
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -377,7 +377,7 @@ describe("SessionV2.create", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("persists repeated switches as distinct durable Session events", () =>
|
||||
it.effect("ignores a model switch when the selected model is unchanged", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* SessionV2.Service
|
||||
const created = yield* session.create({ location })
|
||||
|
|
@ -389,11 +389,29 @@ describe("SessionV2.create", () => {
|
|||
const { db } = yield* Database.Service
|
||||
expect(
|
||||
yield* db.select().from(EventTable).where(eq(EventTable.aggregate_id, created.id)).all().pipe(Effect.orDie),
|
||||
).toHaveLength(3)
|
||||
).toHaveLength(2)
|
||||
expect(yield* session.get(created.id)).toMatchObject({ model })
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("treats an omitted variant as the default variant", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* SessionV2.Service
|
||||
const model = ModelV2.Ref.make({ id: ModelV2.ID.make("sonnet"), providerID: ProviderV2.ID.anthropic })
|
||||
const created = yield* session.create({ location, model })
|
||||
|
||||
yield* session.switchModel({
|
||||
sessionID: created.id,
|
||||
model: ModelV2.Ref.make({ ...model, variant: ModelV2.VariantID.make("default") }),
|
||||
})
|
||||
|
||||
const { db } = yield* Database.Service
|
||||
expect(
|
||||
yield* db.select().from(EventTable).where(eq(EventTable.aggregate_id, created.id)).all().pipe(Effect.orDie),
|
||||
).toHaveLength(1)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("rejects a model switch for a missing Session", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* SessionV2.Service
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { LLMClient } from "@opencode-ai/llm/route"
|
|||
import { DateTime, Effect } from "effect"
|
||||
import { Headers } from "effect/unstable/http"
|
||||
import { Credential } from "@opencode-ai/core/credential"
|
||||
import { Integration } from "@opencode-ai/core/integration"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ProjectV2 } from "@opencode-ai/core/project"
|
||||
|
|
@ -291,6 +292,27 @@ describe("SessionRunnerModel", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("does not project OAuth account metadata into the request body", () =>
|
||||
Effect.gen(function* () {
|
||||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||
ModelV2.Info.make({
|
||||
...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }),
|
||||
request: { headers: {}, body: {} },
|
||||
}),
|
||||
Credential.OAuth.make({
|
||||
type: "oauth",
|
||||
methodID: Integration.MethodID.make("device"),
|
||||
access: "secret",
|
||||
refresh: "refresh",
|
||||
expires: Date.now() + 60_000,
|
||||
metadata: { server: "https://console.example", orgID: "org_123" },
|
||||
}),
|
||||
)
|
||||
|
||||
expect(resolved.route.defaults.http?.body).toEqual({})
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("rejects catalog APIs without a native route", () =>
|
||||
Effect.gen(function* () {
|
||||
const failure = yield* SessionRunnerModel.fromCatalogModel(
|
||||
|
|
|
|||
|
|
@ -149,6 +149,29 @@ describe("ApplyPatchTool", () => {
|
|||
{ type: "update", resource: "update.txt" },
|
||||
{ type: "delete", resource: "remove.txt" },
|
||||
],
|
||||
files: [
|
||||
{
|
||||
file: "nested/new.txt",
|
||||
status: "added",
|
||||
additions: 1,
|
||||
deletions: 0,
|
||||
patch: expect.stringContaining("+created"),
|
||||
},
|
||||
{
|
||||
file: "update.txt",
|
||||
status: "modified",
|
||||
additions: 1,
|
||||
deletions: 1,
|
||||
patch: expect.stringContaining("-before\n+after"),
|
||||
},
|
||||
{
|
||||
file: "remove.txt",
|
||||
status: "deleted",
|
||||
additions: 0,
|
||||
deletions: 1,
|
||||
patch: expect.stringContaining("-remove"),
|
||||
},
|
||||
],
|
||||
})
|
||||
expect(assertions).toMatchObject([
|
||||
{ sessionID, action: "edit", resources: ["nested/new.txt", "update.txt", "remove.txt"], save: ["*"] },
|
||||
|
|
|
|||
|
|
@ -125,11 +125,16 @@ describe("EditTool", () => {
|
|||
value: "Edited file successfully: hello.txt\nReplacements: 1\n```diff\n-before\n+after\n```",
|
||||
})
|
||||
expect(settled.output?.structured).toEqual({
|
||||
operation: "write",
|
||||
target: yield* Effect.promise(() => fs.realpath(target)),
|
||||
resource: "hello.txt",
|
||||
existed: true,
|
||||
replacements: 1,
|
||||
files: [
|
||||
{
|
||||
file: "hello.txt",
|
||||
status: "modified",
|
||||
additions: 1,
|
||||
deletions: 1,
|
||||
patch: expect.stringContaining("-before\n+after"),
|
||||
},
|
||||
],
|
||||
})
|
||||
expect(yield* Effect.promise(() => fs.readFile(target, "utf8"))).toBe("after\nrest\n")
|
||||
expect(assertions).toMatchObject([{ sessionID, action: "edit", resources: ["hello.txt"], save: ["*"] }])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue