Merge remote-tracking branch 'origin/dev' into refactor/tui-v2-api
# Conflicts: # packages/server/src/groups/session.ts # packages/tui/src/component/prompt/autocomplete.tsx # packages/tui/src/context/data.tsx # packages/tui/test/cli/tui/data.test.tsx
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { expect } from "bun:test"
|
||||
import { tool } from "ai"
|
||||
@@ -8,11 +8,6 @@ import { Cause, Effect, Exit, Fiber, Layer, Stream } from "effect"
|
||||
import path from "path"
|
||||
import z from "zod"
|
||||
import type { Agent } from "../../src/agent/agent"
|
||||
import { Agent as AgentSvc } from "../../src/agent/agent"
|
||||
import { Config } from "@/config/config"
|
||||
import { Image } from "@/image/image"
|
||||
import { Permission } from "../../src/permission"
|
||||
import { Plugin } from "../../src/plugin"
|
||||
import { Provider } from "@/provider/provider"
|
||||
|
||||
import { Session } from "@/session/session"
|
||||
@@ -22,7 +17,6 @@ import { SessionProcessor } from "../../src/session/processor"
|
||||
import { MessageID, PartID, SessionID } from "../../src/session/schema"
|
||||
import { SessionStatus } from "../../src/session/status"
|
||||
import { SessionSummary } from "../../src/session/summary"
|
||||
import { Snapshot } from "../../src/snapshot"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { provideTmpdirInstance, provideTmpdirServer } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
@@ -31,6 +25,7 @@ import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||
import { LLMEvent } from "@opencode-ai/llm"
|
||||
|
||||
const summary = Layer.succeed(
|
||||
@@ -171,30 +166,21 @@ const assistant = Effect.fn("TestSession.assistant")(function* (
|
||||
return msg
|
||||
})
|
||||
|
||||
const status = SessionStatus.layer.pipe(Layer.provideMerge(EventV2Bridge.defaultLayer))
|
||||
const infra = Layer.mergeAll(NodeFileSystem.layer, CrossSpawnSpawner.defaultLayer)
|
||||
const deps = Layer.mergeAll(
|
||||
Session.defaultLayer,
|
||||
Snapshot.defaultLayer,
|
||||
AgentSvc.defaultLayer,
|
||||
Permission.defaultLayer,
|
||||
Plugin.defaultLayer,
|
||||
Config.defaultLayer,
|
||||
LLM.defaultLayer,
|
||||
Provider.defaultLayer,
|
||||
status,
|
||||
Database.defaultLayer,
|
||||
EventV2Bridge.defaultLayer,
|
||||
).pipe(Layer.provideMerge(infra))
|
||||
const env = Layer.mergeAll(
|
||||
TestLLMServer.layer,
|
||||
SessionProcessor.layer.pipe(
|
||||
Layer.provide(summary),
|
||||
Layer.provide(Image.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||
Layer.provideMerge(deps),
|
||||
),
|
||||
)
|
||||
const root = LayerNode.group([
|
||||
SessionProcessor.node,
|
||||
Session.node,
|
||||
SessionProjector.node,
|
||||
Provider.node,
|
||||
Database.node,
|
||||
EventV2Bridge.node,
|
||||
SessionStatus.node,
|
||||
CrossSpawnSpawner.node,
|
||||
])
|
||||
const replacements = [
|
||||
LayerNode.replace(SessionSummary.node, summary),
|
||||
LayerNode.replace(RuntimeFlags.node, RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||
]
|
||||
const env = LayerNode.buildLayer(LayerNode.group([root, LayerNode.make(TestLLMServer.layer, [])]), { replacements })
|
||||
|
||||
const it = testEffect(env)
|
||||
|
||||
@@ -218,13 +204,9 @@ const providerErrorLLM = Layer.succeed(
|
||||
),
|
||||
}),
|
||||
)
|
||||
const providerErrorEnv = SessionProcessor.layer.pipe(
|
||||
Layer.provide(summary),
|
||||
Layer.provide(Image.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||
Layer.provide(providerErrorLLM),
|
||||
Layer.provideMerge(deps),
|
||||
)
|
||||
const providerErrorEnv = LayerNode.buildLayer(root, {
|
||||
replacements: [...replacements, LayerNode.replace(LLM.node, providerErrorLLM)],
|
||||
})
|
||||
const itProviderError = testEffect(providerErrorEnv)
|
||||
|
||||
const fragmentFailureLLM = Layer.succeed(
|
||||
@@ -241,13 +223,9 @@ const fragmentFailureLLM = Layer.succeed(
|
||||
),
|
||||
}),
|
||||
)
|
||||
const fragmentFailureEnv = SessionProcessor.layer.pipe(
|
||||
Layer.provide(summary),
|
||||
Layer.provide(Image.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||
Layer.provide(fragmentFailureLLM),
|
||||
Layer.provideMerge(deps),
|
||||
)
|
||||
const fragmentFailureEnv = LayerNode.buildLayer(root, {
|
||||
replacements: [...replacements, LayerNode.replace(LLM.node, fragmentFailureLLM)],
|
||||
})
|
||||
const itFragmentFailure = testEffect(fragmentFailureEnv)
|
||||
|
||||
const boot = Effect.fn("test.boot")(function* () {
|
||||
|
||||
+348
-17
@@ -9958,6 +9958,74 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/location": {
|
||||
"get": {
|
||||
"tags": ["opencode HttpApi"],
|
||||
"operationId": "v2.location.get",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "location",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"directory": {
|
||||
"type": "string"
|
||||
},
|
||||
"workspace": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"required": false,
|
||||
"style": "deepObject",
|
||||
"explode": true
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Location.Info",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LocationInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Resolve the requested location or the server default location.",
|
||||
"summary": "Get location",
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.location.get({\n ...\n})"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/agent": {
|
||||
"get": {
|
||||
"tags": ["opencode HttpApi"],
|
||||
@@ -10163,6 +10231,180 @@
|
||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.session.list({\n ...\n})"
|
||||
}
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"tags": ["sessions"],
|
||||
"operationId": "v2.session.create",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/SessionV2Info"
|
||||
}
|
||||
},
|
||||
"required": ["data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Create a session at the requested location.",
|
||||
"summary": "Create session",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^ses"
|
||||
},
|
||||
"agent": {
|
||||
"type": "string"
|
||||
},
|
||||
"model": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerID": {
|
||||
"type": "string"
|
||||
},
|
||||
"variant": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["id", "providerID"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"location": {
|
||||
"$ref": "#/components/schemas/LocationRef"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.session.create({\n ...\n})"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/session/{sessionID}": {
|
||||
"get": {
|
||||
"tags": ["sessions"],
|
||||
"operationId": "v2.session.get",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "sessionID",
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"pattern": "^ses"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/SessionV2Info"
|
||||
}
|
||||
},
|
||||
"required": ["data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "SessionNotFoundError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/SessionNotFoundError"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SessionNotFoundError"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Retrieve a session by ID.",
|
||||
"summary": "Get session",
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.session.get({\n ...\n})"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/session/{sessionID}/prompt": {
|
||||
@@ -11917,6 +12159,91 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/session/{sessionID}/question": {
|
||||
"get": {
|
||||
"tags": ["session questions"],
|
||||
"operationId": "v2.session.question.list",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "sessionID",
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"pattern": "^ses"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/QuestionV2Request"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "SessionNotFoundError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/SessionNotFoundError"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SessionNotFoundError"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Retrieve pending question requests owned by a session.",
|
||||
"summary": "List session question requests",
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.session.question.list({\n ...\n})"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/session/{sessionID}/question/{requestID}/reply": {
|
||||
"post": {
|
||||
"tags": ["session questions"],
|
||||
@@ -20623,23 +20950,6 @@
|
||||
"required": ["_tag", "message"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ConflictError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"_tag": {
|
||||
"type": "string",
|
||||
"enum": ["ConflictError"]
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"resource": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["_tag", "message"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"SessionNotFoundError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -20657,6 +20967,23 @@
|
||||
"required": ["_tag", "sessionID", "message"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ConflictError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"_tag": {
|
||||
"type": "string",
|
||||
"enum": ["ConflictError"]
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"resource": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["_tag", "message"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ServiceUnavailableError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -28875,6 +29202,10 @@
|
||||
"name": "opencode HttpApi",
|
||||
"description": "Experimental HttpApi surface for selected instance routes."
|
||||
},
|
||||
{
|
||||
"name": "opencode HttpApi",
|
||||
"description": "Experimental HttpApi surface for selected instance routes."
|
||||
},
|
||||
{
|
||||
"name": "sessions",
|
||||
"description": "Experimental session routes."
|
||||
|
||||
@@ -117,14 +117,13 @@ export const SessionGroup = HttpApiGroup.make("server.session")
|
||||
location: Location.Ref.pipe(Schema.optional),
|
||||
}),
|
||||
success: Schema.Struct({ data: SessionV2.Info }),
|
||||
})
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.session.create",
|
||||
summary: "Create session",
|
||||
description: "Create a session at the requested location.",
|
||||
}),
|
||||
),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.session.create",
|
||||
summary: "Create session",
|
||||
description: "Create a session at the requested location.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.get("session.get", "/api/session/:sessionID", {
|
||||
|
||||
@@ -273,6 +273,16 @@ export function Autocomplete(props: {
|
||||
}
|
||||
}
|
||||
|
||||
const references = createMemo(() => data.location.reference.list() ?? [])
|
||||
|
||||
const referenceMatch = createMemo(() => {
|
||||
if (!store.visible || store.visible === "/") return
|
||||
const { baseQuery } = extractLineRange(search())
|
||||
const slash = baseQuery.indexOf("/")
|
||||
const alias = slash === -1 ? baseQuery : baseQuery.slice(0, slash)
|
||||
return references().find((item) => !item.hidden && item.name === alias)
|
||||
})
|
||||
|
||||
function normalizeMentionPath(filePath: string) {
|
||||
const baseDir = sync.path.directory || paths.cwd
|
||||
const absolute = path.resolve(filePath)
|
||||
@@ -303,6 +313,7 @@ export function Autocomplete(props: {
|
||||
() => search(),
|
||||
async (query) => {
|
||||
if (!store.visible || store.visible === "/") return []
|
||||
if (referenceMatch()) return []
|
||||
const { lineRange, baseQuery } = extractLineRange(query ?? "")
|
||||
|
||||
// Get files from SDK
|
||||
@@ -399,6 +410,30 @@ export function Autocomplete(props: {
|
||||
)
|
||||
})
|
||||
|
||||
const referenceAliases = createMemo(() =>
|
||||
references()
|
||||
.filter((reference) => !reference.hidden)
|
||||
.map(
|
||||
(reference): AutocompleteOption => ({
|
||||
display: "@" + reference.name,
|
||||
description: ` ${reference.source.type === "git" ? reference.source.repository : reference.source.path}`,
|
||||
onSelect: () => {
|
||||
insertPart(reference.name, {
|
||||
type: "file",
|
||||
mime: "application/x-directory",
|
||||
filename: reference.name,
|
||||
url: pathToFileURL(reference.path).href,
|
||||
source: {
|
||||
type: "file",
|
||||
text: { start: 0, end: 0, value: "" },
|
||||
path: reference.name,
|
||||
},
|
||||
})
|
||||
},
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
const commands = createMemo((): AutocompleteOption[] => {
|
||||
const results: AutocompleteOption[] = [...slashes()]
|
||||
|
||||
@@ -430,15 +465,21 @@ export function Autocomplete(props: {
|
||||
|
||||
const options = createMemo((prev: AutocompleteOption[] | undefined) => {
|
||||
const filesValue = files()
|
||||
const referenceMatchValue = referenceMatch()
|
||||
const agentsValue = agents()
|
||||
const referenceAliasesValue = referenceAliases()
|
||||
const commandsValue = commands()
|
||||
const searchValue = search()
|
||||
|
||||
if (store.visible === "@" && referenceMatchValue) {
|
||||
return referenceAliasesValue.filter((item) => item.display === `@${referenceMatchValue.name}`)
|
||||
}
|
||||
|
||||
// Files come from fff already fuzzy ranked and filtered
|
||||
// it shouldn't be additionally sorted by fuzzysort as it will loose the results
|
||||
const fileOptions: AutocompleteOption[] = store.visible === "@" ? filesValue || [] : []
|
||||
const nonFileOptions: AutocompleteOption[] =
|
||||
store.visible === "@" ? [...agentsValue, ...mcpResources()] : [...commandsValue]
|
||||
store.visible === "@" ? [...referenceAliasesValue, ...agentsValue, ...mcpResources()] : [...commandsValue]
|
||||
|
||||
if (!searchValue) {
|
||||
return [...nonFileOptions, ...fileOptions]
|
||||
@@ -520,7 +561,7 @@ export function Autocomplete(props: {
|
||||
const endCursor = input.logicalCursor
|
||||
|
||||
input.deleteRange(startCursor.row, startCursor.col, endCursor.row, endCursor.col)
|
||||
input.insertText("@" + path)
|
||||
input.insertText("@" + path + "/")
|
||||
|
||||
setStore("selected", 0)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
PermissionV2Request,
|
||||
ProviderV2Info,
|
||||
QuestionV2Request,
|
||||
ReferenceInfo,
|
||||
SessionMessage,
|
||||
SessionMessageAssistant,
|
||||
SessionMessageAssistantReasoning,
|
||||
@@ -27,6 +28,7 @@ type LocationData = {
|
||||
command?: CommandV2Info[]
|
||||
model?: ModelV2Info[]
|
||||
provider?: ProviderV2Info[]
|
||||
reference?: ReferenceInfo[]
|
||||
skill?: SkillV2Info[]
|
||||
}
|
||||
|
||||
@@ -416,6 +418,9 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
})
|
||||
})
|
||||
break
|
||||
case "reference.updated":
|
||||
void result.location.reference.refresh()
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
@@ -518,6 +523,16 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
setStore("location", key, "provider", result.data.data)
|
||||
},
|
||||
},
|
||||
reference: {
|
||||
list(location?: LocationRef) {
|
||||
return store.location[locationKey(location ?? defaultLocation())]?.reference
|
||||
},
|
||||
async refresh(ref?: LocationRef) {
|
||||
const result = await sdk.client.v2.reference.list({ location: locationQuery(ref) }, { throwOnError: true })
|
||||
const key = locationKey(result.data.location)
|
||||
setStore("location", key, "reference", result.data.data)
|
||||
},
|
||||
},
|
||||
skill: {
|
||||
list(location?: LocationRef) {
|
||||
return store.location[locationKey(location ?? defaultLocation())]?.skill
|
||||
@@ -537,6 +552,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
result.location.agent.refresh(),
|
||||
result.location.model.refresh(),
|
||||
result.location.provider.refresh(),
|
||||
result.location.reference.refresh(),
|
||||
result.location.command.refresh(),
|
||||
result.location.skill.refresh(),
|
||||
])
|
||||
|
||||
@@ -94,7 +94,53 @@ test("refreshes resources into reactive getters", async () => {
|
||||
}
|
||||
})
|
||||
|
||||
test("sync v2 settles pending tools when a live failure arrives", async () => {
|
||||
test("refreshes references after updates", async () => {
|
||||
const events = createEventSource()
|
||||
let requests = 0
|
||||
const calls = createFetch((url) => {
|
||||
if (url.pathname !== "/api/reference") return
|
||||
requests++
|
||||
return json({
|
||||
location: { directory, project: { id: "proj_test", directory } },
|
||||
data: requests === 1 ? [] : [{ name: "docs", path: "/docs", source: { type: "local", path: "/docs" } }],
|
||||
})
|
||||
})
|
||||
let data!: ReturnType<typeof useData>
|
||||
let ready!: () => void
|
||||
const mounted = new Promise<void>((resolve) => {
|
||||
ready = resolve
|
||||
})
|
||||
|
||||
function Probe() {
|
||||
data = useData()
|
||||
onMount(ready)
|
||||
return <box />
|
||||
}
|
||||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider url="http://test" directory={directory} events={events.source} fetch={calls.fetch}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
</DataProvider>
|
||||
</ProjectProvider>
|
||||
</SDKProvider>
|
||||
</TestTuiContexts>
|
||||
))
|
||||
|
||||
try {
|
||||
await mounted
|
||||
await wait(() => requests === 1)
|
||||
emitEvent(events, { id: "evt_reference_1", type: "reference.updated", properties: {} })
|
||||
await wait(() => data.location.reference.list()?.length === 1)
|
||||
expect(data.location.reference.list()?.[0]?.name).toBe("docs")
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
test("settles pending tools when a live failure arrives", async () => {
|
||||
const events = createEventSource()
|
||||
const calls = createFetch()
|
||||
let sync!: ReturnType<typeof useData>
|
||||
@@ -223,7 +269,7 @@ test("sync v2 settles pending tools when a live failure arrives", async () => {
|
||||
}
|
||||
})
|
||||
|
||||
test("sync v2 renders admitted prompts only after promotion", async () => {
|
||||
test("renders admitted prompts only after promotion", async () => {
|
||||
const events = createEventSource()
|
||||
const calls = createFetch()
|
||||
let sync!: ReturnType<typeof useData>
|
||||
@@ -287,7 +333,7 @@ test("sync v2 renders admitted prompts only after promotion", async () => {
|
||||
}
|
||||
})
|
||||
|
||||
test("sync v2 renders a promoted prompt when admission was missed", async () => {
|
||||
test("renders a promoted prompt when admission was missed", async () => {
|
||||
const events = createEventSource()
|
||||
const calls = createFetch()
|
||||
let sync!: ReturnType<typeof useData>
|
||||
@@ -335,7 +381,7 @@ test("sync v2 renders a promoted prompt when admission was missed", async () =>
|
||||
}
|
||||
})
|
||||
|
||||
test("sync v2 projects live context updates with their message ID", async () => {
|
||||
test("projects live context updates with their message ID", async () => {
|
||||
const events = createEventSource()
|
||||
const calls = createFetch()
|
||||
let sync!: ReturnType<typeof useData>
|
||||
|
||||
Reference in New Issue
Block a user