akanjs 3.0.0-alpha.2 → 3.0.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/base/symbols.ts +4 -0
- package/client/clientRuntime.ts +8 -0
- package/client/cn.ts +7 -0
- package/client/csrTypes.ts +2 -0
- package/client/frameDebug.ts +3 -2
- package/common/Logger.ts +4 -0
- package/common/deepObjectify.ts +21 -7
- package/common/index.ts +14 -0
- package/common/mcpExposure.ts +99 -0
- package/common/pathSet.ts +17 -5
- package/common/routeConvention.ts +30 -0
- package/constant/crystalize.ts +3 -1
- package/constant/fieldInfo.ts +6 -0
- package/constant/getDefault.ts +37 -9
- package/constant/immerify.ts +1 -1
- package/constant/index.ts +2 -0
- package/constant/labelOf.ts +17 -0
- package/constant/mask.ts +60 -0
- package/constant/purify.ts +17 -11
- package/constant/types.ts +14 -12
- package/dictionary/DictionaryLookup.ts +30 -0
- package/dictionary/agent.dictionary.ts +31 -0
- package/dictionary/agentTurn.dictionary.ts +29 -0
- package/dictionary/base.dictionary.ts +8 -0
- package/dictionary/dictInfo.ts +8 -0
- package/dictionary/dictionary.ts +9 -3
- package/dictionary/index.ts +1 -0
- package/document/database.ts +21 -0
- package/document/filterMeta.ts +7 -0
- package/document/index.ts +1 -0
- package/document/into.ts +7 -1
- package/document/noDocumentError.ts +12 -0
- package/fetch/agentTurn.ts +19 -0
- package/fetch/client/fetchClient.ts +71 -13
- package/fetch/client/wsClient.ts +17 -2
- package/fetch/fetchType/endpointFetch.type.ts +10 -4
- package/fetch/index.ts +1 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-wal +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-wal +0 -0
- package/package.json +11 -9
- package/server/akanApp.ts +30 -34
- package/server/akanOption.ts +65 -7
- package/server/akanServer.ts +172 -7
- package/server/animatedImage.ts +83 -0
- package/server/artifact/routeClientCache.ts +14 -0
- package/server/assetEncoding.ts +47 -0
- package/server/devtools/signalSerializer.ts +2 -2
- package/server/devtools/types.ts +2 -2
- package/server/di/diLifecycle.ts +31 -3
- package/server/di/predefinedAdaptor.ts +6 -0
- package/server/imageOptimizer.ts +33 -46
- package/server/index.ts +1 -2
- package/server/mcp/McpAuth.ts +177 -0
- package/server/mcp/McpDispatcher.ts +245 -0
- package/server/mcp/McpEventStream.ts +76 -0
- package/server/mcp/McpExecutionContext.ts +105 -0
- package/server/mcp/McpRouter.ts +591 -0
- package/server/mcp/index.ts +5 -0
- package/server/resolver/CascadeRunner.ts +1 -1
- package/server/resolver/database.resolver.ts +42 -5
- package/server/resolver/service.resolver.ts +29 -0
- package/server/resolver/signal.resolver.ts +16 -1
- package/server/routeTreeBuilder.ts +2 -44
- package/server/rscWorker.tsx +6 -2
- package/server/subRouteIndexDocument.tsx +61 -0
- package/server/systemPageDocument.tsx +2 -2
- package/server/systemPages.tsx +49 -16
- package/server/vendor/akanjs-fetch.ts +1 -0
- package/server/webRouter.ts +159 -35
- package/service/agent.service.ts +14 -0
- package/service/base.service.ts +2 -1
- package/service/index.ts +1 -0
- package/service/predefinedAdaptor/database.adaptor.ts +78 -24
- package/service/predefinedAdaptor/deepseekLlm.ts +230 -0
- package/service/predefinedAdaptor/index.ts +3 -0
- package/service/predefinedAdaptor/insightQuery.ts +183 -0
- package/service/predefinedAdaptor/llm.adaptor.ts +76 -0
- package/service/predefinedAdaptor/role.adaptor.ts +2 -0
- package/service/predefinedAdaptor/schedule.adaptor.ts +12 -5
- package/service/serviceModule.ts +17 -0
- package/service/types.ts +4 -0
- package/signal/agent/AgentCatalogue.ts +165 -0
- package/signal/agent/index.ts +1 -0
- package/signal/agent.signal.ts +42 -0
- package/signal/agentTurn.ts +8 -0
- package/signal/agentTurnStream.ts +48 -0
- package/signal/base.signal.ts +2 -1
- package/signal/endpointInfo.ts +38 -2
- package/signal/guard.ts +13 -1
- package/signal/guards.ts +52 -1
- package/signal/index.ts +6 -0
- package/signal/mcp/McpDocument.ts +286 -0
- package/signal/mcp/McpProgress.ts +106 -0
- package/signal/mcp/McpUriTemplate.ts +85 -0
- package/signal/mcp/Msg.ts +368 -0
- package/signal/mcp/index.ts +5 -0
- package/signal/mcp/mcpProtocol.ts +120 -0
- package/signal/middleware.ts +7 -3
- package/signal/openapi/openapi.ts +32 -171
- package/signal/schema/JsonSchemaBuilder.ts +211 -0
- package/signal/schema/index.ts +1 -0
- package/signal/signalContext.ts +85 -9
- package/signal/slice.ts +9 -9
- package/signal/types.ts +1 -1
- package/store/action.ts +11 -22
- package/store/actionTag.ts +33 -0
- package/store/agent/AgentBridge.ts +323 -0
- package/store/agent/AgentContext.ts +74 -0
- package/store/agent/AgentPrompts.ts +82 -0
- package/store/agent/AgentVisibility.ts +68 -0
- package/store/agent/ScreenReader.ts +208 -0
- package/store/agent/StoreCatalogue.ts +351 -0
- package/store/agent/StoreSurfaceSource.ts +122 -0
- package/store/agent/index.ts +9 -0
- package/store/agent/storeSurface.ts +28 -0
- package/store/agent/types.ts +50 -0
- package/store/agentic/StToolBuilder.ts +199 -0
- package/store/agentic/attachAgentic.ts +22 -0
- package/store/agentic/index.ts +5 -0
- package/store/agentic/readableValue.ts +17 -0
- package/store/agentic/useStExpose.ts +18 -0
- package/store/agentic/useStState.ts +35 -0
- package/store/baseSt.ts +7 -0
- package/store/databaseStateNames.ts +31 -0
- package/store/formSetterNames.ts +21 -0
- package/store/index.ts +8 -0
- package/store/rootStore.ts +3 -1
- package/store/sliceRole.ts +36 -0
- package/store/state.ts +2 -12
- package/store/store.ts +19 -1
- package/store/storeInstance.ts +165 -20
- package/store/storeRegistry.ts +16 -1
- package/store/types.ts +18 -0
- package/store/withSelector.ts +7 -8
- package/test/registerDom.ts +4 -0
- package/types/base/symbols.d.ts +4 -0
- package/types/client/cn.d.ts +5 -0
- package/types/client/csrTypes.d.ts +2 -0
- package/types/common/Logger.d.ts +2 -0
- package/types/common/deepObjectify.d.ts +4 -2
- package/types/common/index.d.ts +2 -1
- package/types/common/mcpExposure.d.ts +60 -0
- package/types/common/routeConvention.d.ts +8 -0
- package/types/constant/fieldInfo.d.ts +1 -0
- package/types/constant/getDefault.d.ts +5 -0
- package/types/constant/index.d.ts +2 -0
- package/types/constant/labelOf.d.ts +6 -0
- package/types/constant/mask.d.ts +34 -0
- package/types/constant/purify.d.ts +2 -2
- package/types/constant/types.d.ts +2 -2
- package/types/dictionary/DictionaryLookup.d.ts +14 -0
- package/types/dictionary/agent.dictionary.d.ts +1 -0
- package/types/dictionary/agentTurn.dictionary.d.ts +1 -0
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictInfo.d.ts +6 -0
- package/types/dictionary/dictionary.d.ts +11 -9
- package/types/dictionary/index.d.ts +1 -0
- package/types/document/database.d.ts +20 -0
- package/types/document/filterMeta.d.ts +1 -0
- package/types/document/index.d.ts +1 -0
- package/types/document/into.d.ts +6 -1
- package/types/document/noDocumentError.d.ts +12 -0
- package/types/fetch/agentTurn.d.ts +593 -0
- package/types/fetch/client/fetchClient.d.ts +10 -0
- package/types/fetch/fetchType/endpointFetch.type.d.ts +5 -3
- package/types/fetch/index.d.ts +1 -0
- package/types/server/akanOption.d.ts +35 -7
- package/types/server/akanServer.d.ts +44 -1
- package/types/server/animatedImage.d.ts +7 -0
- package/types/server/artifact/routeClientCache.d.ts +6 -0
- package/types/server/assetEncoding.d.ts +7 -0
- package/types/server/devtools/types.d.ts +2 -2
- package/types/server/di/predefinedAdaptor.d.ts +4 -1
- package/types/server/index.d.ts +0 -2
- package/types/server/mcp/McpAuth.d.ts +62 -0
- package/types/server/mcp/McpDispatcher.d.ts +54 -0
- package/types/server/mcp/McpEventStream.d.ts +18 -0
- package/types/server/mcp/McpExecutionContext.d.ts +44 -0
- package/types/server/mcp/McpRouter.d.ts +60 -0
- package/types/server/mcp/index.d.ts +5 -0
- package/types/server/mcp.d.ts +1 -0
- package/types/server/subRouteIndexDocument.d.ts +8 -0
- package/types/server/systemPageDocument.d.ts +1 -0
- package/types/server/systemPages.d.ts +5 -0
- package/types/server/vendor/akanjs-fetch.d.ts +1 -0
- package/types/service/agent.service.d.ts +12 -0
- package/types/service/base.service.d.ts +4 -0
- package/types/service/index.d.ts +1 -0
- package/types/service/predefinedAdaptor/database.adaptor.d.ts +24 -2
- package/types/service/predefinedAdaptor/deepseekLlm.d.ts +64 -0
- package/types/service/predefinedAdaptor/index.d.ts +3 -0
- package/types/service/predefinedAdaptor/insightQuery.d.ts +50 -0
- package/types/service/predefinedAdaptor/llm.adaptor.d.ts +68 -0
- package/types/service/predefinedAdaptor/role.adaptor.d.ts +2 -0
- package/types/service/predefinedAdaptor/schedule.adaptor.d.ts +10 -6
- package/types/service/types.d.ts +4 -1
- package/types/signal/agent/AgentCatalogue.d.ts +100 -0
- package/types/signal/agent/index.d.ts +1 -0
- package/types/signal/agent.d.ts +1 -0
- package/types/signal/agent.signal.d.ts +29 -0
- package/types/signal/agentTurn.d.ts +9 -0
- package/types/signal/agentTurnStream.d.ts +16 -0
- package/types/signal/base.signal.d.ts +1 -1
- package/types/signal/endpointInfo.d.ts +21 -3
- package/types/signal/guard.d.ts +11 -0
- package/types/signal/guards.d.ts +22 -1
- package/types/signal/index.d.ts +6 -0
- package/types/signal/mcp/McpDocument.d.ts +65 -0
- package/types/signal/mcp/McpProgress.d.ts +40 -0
- package/types/signal/mcp/McpUriTemplate.d.ts +26 -0
- package/types/signal/mcp/Msg.d.ts +143 -0
- package/types/signal/mcp/index.d.ts +5 -0
- package/types/signal/mcp/mcpProtocol.d.ts +107 -0
- package/types/signal/mcp.d.ts +1 -0
- package/types/signal/openapi/openapi.d.ts +3 -3
- package/types/signal/schema/JsonSchemaBuilder.d.ts +47 -0
- package/types/signal/schema/index.d.ts +1 -0
- package/types/signal/schema.d.ts +1 -0
- package/types/signal/signalContext.d.ts +16 -1
- package/types/signal/slice.d.ts +8 -8
- package/types/signal/types.d.ts +1 -1
- package/types/store/actionTag.d.ts +21 -0
- package/types/store/agent/AgentBridge.d.ts +77 -0
- package/types/store/agent/AgentContext.d.ts +16 -0
- package/types/store/agent/AgentPrompts.d.ts +31 -0
- package/types/store/agent/AgentVisibility.d.ts +21 -0
- package/types/store/agent/ScreenReader.d.ts +11 -0
- package/types/store/agent/StoreCatalogue.d.ts +26 -0
- package/types/store/agent/StoreSurfaceSource.d.ts +18 -0
- package/types/store/agent/index.d.ts +9 -0
- package/types/store/agent/storeSurface.d.ts +14 -0
- package/types/store/agent/types.d.ts +49 -0
- package/types/store/agent.d.ts +1 -0
- package/types/store/agentic/StToolBuilder.d.ts +41 -0
- package/types/store/agentic/attachAgentic.d.ts +14 -0
- package/types/store/agentic/index.d.ts +5 -0
- package/types/store/agentic/readableValue.d.ts +3 -0
- package/types/store/agentic/useStExpose.d.ts +8 -0
- package/types/store/agentic/useStState.d.ts +12 -0
- package/types/store/agentic.d.ts +1 -0
- package/types/store/baseSt.d.ts +29 -22
- package/types/store/databaseStateNames.d.ts +25 -0
- package/types/store/formSetterNames.d.ts +16 -0
- package/types/store/index.d.ts +7 -0
- package/types/store/rootStore.d.ts +5 -1
- package/types/store/sliceRole.d.ts +25 -0
- package/types/store/store.d.ts +7 -2
- package/types/store/storeInstance.d.ts +33 -1
- package/types/store/storeRegistry.d.ts +5 -0
- package/types/store/types.d.ts +15 -0
- package/types/store/withSelector.d.ts +7 -8
- package/types/test/registerDom.d.ts +1 -0
- package/types/ui/Agent/Approval.d.ts +7 -0
- package/types/ui/Agent/Bubble.d.ts +13 -0
- package/types/ui/Agent/Chat.d.ts +24 -0
- package/types/ui/Agent/Context.d.ts +9 -0
- package/types/ui/Agent/Dock.d.ts +16 -0
- package/types/ui/Agent/Guide.d.ts +9 -0
- package/types/ui/Agent/Section.d.ts +10 -0
- package/types/ui/Agent/StateKey.d.ts +16 -0
- package/types/ui/Agent/Tool.d.ts +15 -0
- package/types/ui/Agent/Transcript.d.ts +13 -0
- package/types/ui/Agent/Zone.d.ts +23 -0
- package/types/ui/Agent/fetchRunner.d.ts +12 -0
- package/types/ui/Agent/index.d.ts +17 -0
- package/types/ui/Agent/index_.d.ts +1 -0
- package/types/ui/Agent/sessionHistory.d.ts +12 -0
- package/types/ui/Agent.d.ts +1 -0
- package/types/ui/Badge.d.ts +1 -1
- package/types/ui/Button.d.ts +1 -1
- package/types/ui/CsrImage.d.ts +1 -1
- package/types/ui/Dropdown.d.ts +2 -0
- package/types/ui/Image.d.ts +3 -3
- package/types/ui/Layout/BottomInset.d.ts +2 -1
- package/types/ui/Layout/index.d.ts +1 -1
- package/types/ui/Signal/style.d.ts +4 -1
- package/types/ui/UiOverride/context.d.ts +14 -2
- package/types/ui/agentAttrs.d.ts +14 -0
- package/types/ui/index.d.ts +4 -1
- package/types/ui/overlayLayer.d.ts +24 -0
- package/types/ui/recipe/badgeRecipe.d.ts +7 -3
- package/types/ui/recipe/buttonRecipe.d.ts +7 -3
- package/types/ui/recipe/inputRecipe.d.ts +5 -1
- package/types/vendor/use-agentic/AgentProvider.d.ts +18 -0
- package/types/vendor/use-agentic/AgentScope.d.ts +9 -0
- package/types/vendor/use-agentic/AgentSession.d.ts +50 -0
- package/types/vendor/use-agentic/Agentic.d.ts +21 -0
- package/types/vendor/use-agentic/AgenticSurface.d.ts +44 -0
- package/types/vendor/use-agentic/httpRunner.d.ts +15 -0
- package/types/vendor/use-agentic/index.d.ts +13 -0
- package/types/vendor/use-agentic/surfaceContext.d.ts +5 -0
- package/types/vendor/use-agentic/types.d.ts +133 -0
- package/types/vendor/use-agentic/useAgent.d.ts +4 -0
- package/types/vendor/use-agentic/useAgentGuide.d.ts +6 -0
- package/types/vendor/use-agentic/useAgentResource.d.ts +7 -0
- package/types/vendor/use-agentic/useAgentState.d.ts +13 -0
- package/types/vendor/use-agentic/useAgentTool.d.ts +16 -0
- package/types/vendor/use-agentic.d.ts +1 -0
- package/types/webkit/index.d.ts +3 -0
- package/types/webkit/lazy.d.ts +12 -0
- package/types/webkit/useCsrValues.d.ts +3 -3
- package/types/webkit/useEscapeKey.d.ts +5 -0
- package/types/webkit/useFrameRuntime.d.ts +6 -1
- package/types/webkit/useScreenScope.d.ts +18 -0
- package/ui/Agent/Approval.tsx +29 -0
- package/ui/Agent/Bubble.tsx +74 -0
- package/ui/Agent/Chat.tsx +225 -0
- package/ui/Agent/Context.tsx +38 -0
- package/ui/Agent/Dock.tsx +72 -0
- package/ui/Agent/Guide.tsx +16 -0
- package/ui/Agent/Section.tsx +24 -0
- package/ui/Agent/StateKey.tsx +44 -0
- package/ui/Agent/Tool.tsx +66 -0
- package/ui/Agent/Transcript.tsx +33 -0
- package/ui/Agent/Zone.tsx +48 -0
- package/ui/Agent/fetchRunner.ts +30 -0
- package/ui/Agent/index.ts +12 -0
- package/ui/Agent/index_.tsx +4 -0
- package/ui/Agent/sessionHistory.ts +33 -0
- package/ui/Badge.tsx +3 -3
- package/ui/BottomSheet.tsx +5 -0
- package/ui/Button.tsx +6 -2
- package/ui/Constant/Doc.tsx +1 -1
- package/ui/CsrImage.tsx +1 -1
- package/ui/Data/ListContainer.tsx +1 -1
- package/ui/DatePicker.tsx +5 -4
- package/ui/Dialog/Modal.tsx +12 -15
- package/ui/DraggableList.tsx +3 -1
- package/ui/Dropdown.tsx +33 -11
- package/ui/Field.tsx +30 -16
- package/ui/Image.tsx +3 -3
- package/ui/Input.tsx +14 -5
- package/ui/Layout/BottomInset.tsx +6 -1
- package/ui/Load/Units.tsx +11 -2
- package/ui/Load/View.tsx +7 -2
- package/ui/Loading/ProgressBar.tsx +8 -1
- package/ui/Menu.tsx +7 -8
- package/ui/Model/EditModal.tsx +37 -2
- package/ui/Model/SureToRemove.tsx +2 -1
- package/ui/Model/index_.tsx +42 -15
- package/ui/ObjectId.tsx +3 -4
- package/ui/Pagination.tsx +3 -4
- package/ui/Popconfirm.tsx +8 -7
- package/ui/Select.tsx +6 -4
- package/ui/Signal/RestApi.tsx +43 -16
- package/ui/Signal/WebSocket.tsx +1 -1
- package/ui/Signal/style.ts +6 -1
- package/ui/Switch.tsx +2 -0
- package/ui/System/CSR.tsx +6 -2
- package/ui/System/ThemeToggle.tsx +55 -33
- package/ui/ToggleSelect.tsx +4 -3
- package/ui/Tooltip.tsx +2 -1
- package/ui/UiOverride/context.ts +14 -2
- package/ui/agentAttrs.ts +19 -0
- package/ui/index.ts +10 -1
- package/ui/overlayLayer.ts +39 -0
- package/ui/recipe/badgeRecipe.ts +22 -3
- package/ui/recipe/buttonRecipe.ts +51 -2
- package/ui/recipe/factory.ts +2 -2
- package/ui/recipe/inputRecipe.ts +18 -4
- package/vendor/use-agentic/AgentProvider.tsx +39 -0
- package/vendor/use-agentic/AgentScope.tsx +20 -0
- package/vendor/use-agentic/AgentSession.ts +292 -0
- package/vendor/use-agentic/Agentic.tsx +44 -0
- package/vendor/use-agentic/AgenticSurface.ts +294 -0
- package/vendor/use-agentic/WIRE.md +57 -0
- package/vendor/use-agentic/httpRunner.ts +115 -0
- package/vendor/use-agentic/index.ts +14 -0
- package/vendor/use-agentic/surfaceContext.ts +9 -0
- package/vendor/use-agentic/types.ts +144 -0
- package/vendor/use-agentic/useAgent.ts +17 -0
- package/vendor/use-agentic/useAgentGuide.ts +16 -0
- package/vendor/use-agentic/useAgentResource.ts +30 -0
- package/vendor/use-agentic/useAgentState.ts +61 -0
- package/vendor/use-agentic/useAgentTool.ts +55 -0
- package/webkit/bootCsr.tsx +2 -33
- package/webkit/index.ts +3 -0
- package/webkit/lazy.tsx +27 -3
- package/webkit/useCsrValues.ts +121 -4
- package/webkit/useEscapeKey.tsx +42 -0
- package/webkit/useFrameRuntime.ts +65 -32
- package/webkit/useScreenScope.tsx +49 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PublishedResource,
|
|
3
|
+
PublishedTool,
|
|
4
|
+
ResourceDiff,
|
|
5
|
+
ResourceEntry,
|
|
6
|
+
ScopeEntry,
|
|
7
|
+
SurfaceSnapshot,
|
|
8
|
+
SurfaceSource,
|
|
9
|
+
SurfaceView,
|
|
10
|
+
ToolEntry,
|
|
11
|
+
} from "./types";
|
|
12
|
+
|
|
13
|
+
const SHARED_KEY = Symbol.for("useAgentic.sharedSurface");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The live registry of what the mounted screen offers an agent: tools, readable resources, and scopes.
|
|
17
|
+
*
|
|
18
|
+
* Entries come and go with component lifetimes, so unlike a static catalogue this is subscribable and every
|
|
19
|
+
* snapshot answers "now". A registration stack backs each name — remounts are ordinary React behavior, so a
|
|
20
|
+
* duplicate name warns and the newest wins instead of throwing, and unregistering restores what it shadowed.
|
|
21
|
+
*/
|
|
22
|
+
export class AgenticSurface {
|
|
23
|
+
/** One surface per runtime unless a provider isolates its own. Keyed on `globalThis` so duplicated module copies still meet. */
|
|
24
|
+
static get shared(): AgenticSurface {
|
|
25
|
+
const holder = globalThis as typeof globalThis & { [SHARED_KEY]?: AgenticSurface };
|
|
26
|
+
holder[SHARED_KEY] ??= new AgenticSurface();
|
|
27
|
+
return holder[SHARED_KEY];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** MCP tool names allow `[A-Za-z0-9_.-]`, and `.` is the scope join — so every part is folded into `[A-Za-z0-9_-]`. */
|
|
31
|
+
static sanitize(part: string) {
|
|
32
|
+
return part.replace(/[^A-Za-z0-9_-]/g, "-");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static childPath(parent: string[], id: string) {
|
|
36
|
+
return [...parent, AgenticSurface.sanitize(id)];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static fullName(scope: string[], name: string) {
|
|
40
|
+
return [...scope.map((part) => AgenticSurface.sanitize(part)), AgenticSurface.sanitize(name)].join(".");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#tools = new Map<string, ToolEntry[]>();
|
|
44
|
+
#resources = new Map<string, ResourceEntry[]>();
|
|
45
|
+
#scopes = new Map<string, ScopeEntry[]>();
|
|
46
|
+
#guides: { scope: string; text: string }[] = [];
|
|
47
|
+
#sources = new Set<SurfaceSource>();
|
|
48
|
+
#listeners = new Set<() => void>();
|
|
49
|
+
#warned = new Set<string>();
|
|
50
|
+
|
|
51
|
+
subscribe(listener: () => void) {
|
|
52
|
+
this.#listeners.add(listener);
|
|
53
|
+
return () => {
|
|
54
|
+
this.#listeners.delete(listener);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
registerTool(scope: string[], entry: ToolEntry) {
|
|
59
|
+
return this.#stack(this.#tools, AgenticSurface.fullName(scope, entry.name), entry);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
registerResource(scope: string[], entry: ResourceEntry) {
|
|
63
|
+
return this.#stack(this.#resources, AgenticSurface.fullName(scope, entry.name), entry);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
openScope(parent: string[], scope: ScopeEntry) {
|
|
67
|
+
return this.#stack(this.#scopes, AgenticSurface.childPath(parent, scope.id).join("."), scope);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Standing guidance for the agent while the registrant is mounted — instructions, not context data. Kept in
|
|
72
|
+
* registration order rather than sorted: guides are prose, and each one must stay a coherent block.
|
|
73
|
+
*/
|
|
74
|
+
registerGuide(scope: string[], text: string) {
|
|
75
|
+
const entry = { scope: scope.join("."), text };
|
|
76
|
+
this.#guides.push(entry);
|
|
77
|
+
this.#notify();
|
|
78
|
+
return () => {
|
|
79
|
+
const idx = this.#guides.indexOf(entry);
|
|
80
|
+
if (idx >= 0) this.#guides.splice(idx, 1);
|
|
81
|
+
this.#notify();
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
addSource(source: SurfaceSource) {
|
|
86
|
+
this.#sources.add(source);
|
|
87
|
+
const unsubscribe = source.subscribe?.(() => this.#notify());
|
|
88
|
+
this.#notify();
|
|
89
|
+
return () => {
|
|
90
|
+
this.#sources.delete(source);
|
|
91
|
+
unsubscribe?.();
|
|
92
|
+
this.#notify();
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
snapshot(view: string[] = []): SurfaceSnapshot {
|
|
97
|
+
const viewKey = view.join(".");
|
|
98
|
+
const tools = [...this.#activeTools(view)].map(([name, entry]) => AgenticSurface.#publishTool(name, entry));
|
|
99
|
+
const resources = [...this.#activeResources(view)].map(([name, entry]) =>
|
|
100
|
+
AgenticSurface.#publishResource(name, entry),
|
|
101
|
+
);
|
|
102
|
+
const scopes = [...this.#scopes.entries()]
|
|
103
|
+
.filter(([path]) => AgenticSurface.#within(viewKey, path))
|
|
104
|
+
.map(([path, stack]) => {
|
|
105
|
+
const scope = stack[stack.length - 1];
|
|
106
|
+
return { path, ...(scope.label ? { label: scope.label } : {}), ...(scope.kind ? { kind: scope.kind } : {}) };
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
tools: tools.sort((a, b) => (a.name < b.name ? -1 : 1)),
|
|
111
|
+
resources: resources.sort((a, b) => (a.name < b.name ? -1 : 1)),
|
|
112
|
+
scopes: scopes.sort((a, b) => (a.path < b.path ? -1 : 1)),
|
|
113
|
+
guides: this.#guides.filter((guide) => AgenticSurface.#guideApplies(viewKey, guide.scope)).map((g) => g.text),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* A zone session's reading half: the same registry filtered to one scope subtree. Hook entries filter by their
|
|
119
|
+
* scope-prefixed name, sources decide for themselves per view, and guides follow the layout cascade — a zone
|
|
120
|
+
* reads its ancestors' guidance plus its own, never a sibling's. The empty path is the surface itself.
|
|
121
|
+
*/
|
|
122
|
+
view(path: string[]): SurfaceView {
|
|
123
|
+
if (!path.length) return this;
|
|
124
|
+
return {
|
|
125
|
+
snapshot: () => this.snapshot(path),
|
|
126
|
+
tool: (name) => this.tool(name, path),
|
|
127
|
+
call: (name, args) => this.call(name, args, path),
|
|
128
|
+
read: (name) => this.read(name, path),
|
|
129
|
+
diffSince: (before) => this.diffSince(before, path),
|
|
130
|
+
subscribe: (listener) => this.subscribe(listener),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
tool(name: string, view: string[] = []): ToolEntry | null {
|
|
135
|
+
const viewKey = view.join(".");
|
|
136
|
+
const stack = this.#tools.get(name);
|
|
137
|
+
if (stack?.length && AgenticSurface.#within(viewKey, name)) return stack[stack.length - 1];
|
|
138
|
+
for (const source of this.#sources) {
|
|
139
|
+
const found = source.tools?.(view).find((candidate) => candidate.name === name);
|
|
140
|
+
if (found) return found;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async call(name: string, args: Record<string, unknown> = {}, view: string[] = []): Promise<unknown> {
|
|
146
|
+
const entry = this.tool(name, view);
|
|
147
|
+
if (!entry) throw new Error(`Unknown tool: ${name}`);
|
|
148
|
+
const verdict = entry.guard?.(args) ?? true;
|
|
149
|
+
if (verdict !== true) throw new Error(verdict);
|
|
150
|
+
return await entry.run(args);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
read(name: string, view: string[] = []): unknown {
|
|
154
|
+
const entry = this.#resource(name, view);
|
|
155
|
+
if (!entry) throw new Error(`Unknown resource: ${name}`);
|
|
156
|
+
return entry.read();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The resources that changed since `before`, for reporting a tool call's effect back to the model.
|
|
161
|
+
*
|
|
162
|
+
* Runs against the live entries rather than a second snapshot because `report: false` lives on the entry, and a
|
|
163
|
+
* snapshot deliberately does not publish it.
|
|
164
|
+
*/
|
|
165
|
+
diffSince(before: SurfaceSnapshot, view: string[] = []): ResourceDiff[] {
|
|
166
|
+
const prev = new Map(before.resources.map((resource) => [resource.name, resource]));
|
|
167
|
+
const diffs: ResourceDiff[] = [];
|
|
168
|
+
const seen = new Set<string>();
|
|
169
|
+
for (const [name, entry] of this.#activeResources(view)) {
|
|
170
|
+
seen.add(name);
|
|
171
|
+
if (entry.report === false) continue;
|
|
172
|
+
const current = AgenticSurface.#publishResource(name, entry);
|
|
173
|
+
const last = prev.get(name);
|
|
174
|
+
if (
|
|
175
|
+
last &&
|
|
176
|
+
AgenticSurface.#print(last.value) === AgenticSurface.#print(current.value) &&
|
|
177
|
+
last.error === current.error
|
|
178
|
+
)
|
|
179
|
+
continue;
|
|
180
|
+
diffs.push({
|
|
181
|
+
name,
|
|
182
|
+
...(current.value !== undefined ? { value: current.value } : {}),
|
|
183
|
+
...(current.error !== undefined ? { error: current.error } : {}),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
for (const name of prev.keys()) if (!seen.has(name)) diffs.push({ name, removed: true });
|
|
187
|
+
return diffs;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
*#activeTools(view: string[] = []): Generator<[string, ToolEntry]> {
|
|
191
|
+
const viewKey = view.join(".");
|
|
192
|
+
const seen = new Set<string>();
|
|
193
|
+
for (const [name, stack] of this.#tools) {
|
|
194
|
+
if (!AgenticSurface.#within(viewKey, name)) continue;
|
|
195
|
+
seen.add(name);
|
|
196
|
+
yield [name, stack[stack.length - 1]];
|
|
197
|
+
}
|
|
198
|
+
for (const source of this.#sources) {
|
|
199
|
+
for (const entry of source.tools?.(view) ?? []) {
|
|
200
|
+
if (seen.has(entry.name)) continue;
|
|
201
|
+
seen.add(entry.name);
|
|
202
|
+
yield [entry.name, entry];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
*#activeResources(view: string[] = []): Generator<[string, ResourceEntry]> {
|
|
208
|
+
const viewKey = view.join(".");
|
|
209
|
+
const seen = new Set<string>();
|
|
210
|
+
for (const [name, stack] of this.#resources) {
|
|
211
|
+
if (!AgenticSurface.#within(viewKey, name)) continue;
|
|
212
|
+
seen.add(name);
|
|
213
|
+
yield [name, stack[stack.length - 1]];
|
|
214
|
+
}
|
|
215
|
+
for (const source of this.#sources) {
|
|
216
|
+
for (const entry of source.resources?.(view) ?? []) {
|
|
217
|
+
if (seen.has(entry.name)) continue;
|
|
218
|
+
seen.add(entry.name);
|
|
219
|
+
yield [entry.name, entry];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#resource(name: string, view: string[] = []): ResourceEntry | null {
|
|
225
|
+
const viewKey = view.join(".");
|
|
226
|
+
const stack = this.#resources.get(name);
|
|
227
|
+
if (stack?.length && AgenticSurface.#within(viewKey, name)) return stack[stack.length - 1];
|
|
228
|
+
for (const source of this.#sources) {
|
|
229
|
+
const found = source.resources?.(view).find((candidate) => candidate.name === name);
|
|
230
|
+
if (found) return found;
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Empty view sees everything; a zone view sees the entries whose scope-prefixed key sits in its subtree. */
|
|
236
|
+
static #within(viewKey: string, key: string) {
|
|
237
|
+
return !viewKey || key === viewKey || key.startsWith(`${viewKey}.`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** The layout cascade: a guide applies to a view when one contains the other — ancestors and descendants, never siblings. */
|
|
241
|
+
static #guideApplies(viewKey: string, scopeKey: string) {
|
|
242
|
+
if (!viewKey || !scopeKey || viewKey === scopeKey) return true;
|
|
243
|
+
return viewKey.startsWith(`${scopeKey}.`) || scopeKey.startsWith(`${viewKey}.`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
#stack<E>(map: Map<string, E[]>, key: string, entry: E) {
|
|
247
|
+
const stack = map.get(key) ?? [];
|
|
248
|
+
if (stack.length && !this.#warned.has(key)) {
|
|
249
|
+
this.#warned.add(key);
|
|
250
|
+
console.warn(`[use-agentic] "${key}" is registered more than once; the newest registration wins.`);
|
|
251
|
+
}
|
|
252
|
+
stack.push(entry);
|
|
253
|
+
map.set(key, stack);
|
|
254
|
+
this.#notify();
|
|
255
|
+
return () => {
|
|
256
|
+
const idx = stack.indexOf(entry);
|
|
257
|
+
if (idx >= 0) stack.splice(idx, 1);
|
|
258
|
+
if (!stack.length) map.delete(key);
|
|
259
|
+
this.#notify();
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
#notify() {
|
|
264
|
+
for (const listener of this.#listeners) listener();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
static #publishTool(name: string, entry: ToolEntry): PublishedTool {
|
|
268
|
+
return {
|
|
269
|
+
name,
|
|
270
|
+
...(entry.description ? { description: entry.description } : {}),
|
|
271
|
+
...(entry.parameters ? { parameters: entry.parameters } : {}),
|
|
272
|
+
...(entry.effect ? { effect: entry.effect } : {}),
|
|
273
|
+
needsConfirm: entry.confirm !== undefined && entry.confirm !== false,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
static #publishResource(name: string, entry: ResourceEntry): PublishedResource {
|
|
278
|
+
const base = { name, ...(entry.description ? { description: entry.description } : {}) };
|
|
279
|
+
try {
|
|
280
|
+
return { ...base, value: entry.read() };
|
|
281
|
+
} catch (error) {
|
|
282
|
+
return { ...base, error: error instanceof Error ? error.message : String(error) };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Serialized equality, because a read may rebuild an equal object on every call. */
|
|
287
|
+
static #print(value: unknown) {
|
|
288
|
+
try {
|
|
289
|
+
return JSON.stringify(value) ?? "undefined";
|
|
290
|
+
} catch {
|
|
291
|
+
return "[unserializable]";
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# The turn wire
|
|
2
|
+
|
|
3
|
+
`httpRunner` speaks this contract. Any backend that answers it can drive an `AgentSession` — the session, the
|
|
4
|
+
tools, and the loop stay in the client; the server serves exactly one stateless model turn per request.
|
|
5
|
+
|
|
6
|
+
## Request
|
|
7
|
+
|
|
8
|
+
`POST <url>` with `content-type: application/json`:
|
|
9
|
+
|
|
10
|
+
```jsonc
|
|
11
|
+
{
|
|
12
|
+
"messages": [
|
|
13
|
+
{ "role": "user", "text": "Trim the intro scene" },
|
|
14
|
+
{ "role": "assistant", "text": "", "toolCalls": [{ "id": "c1", "name": "setSceneSeconds", "args": { "seconds": 4 } }] },
|
|
15
|
+
{ "role": "tool", "toolResults": [{ "id": "c1", "name": "setSceneSeconds", "changes": [{ "name": "totalSeconds", "value": 39 }] }] }
|
|
16
|
+
],
|
|
17
|
+
"tools": [
|
|
18
|
+
{
|
|
19
|
+
"name": "setSceneSeconds",
|
|
20
|
+
"description": "Change this scene's length in seconds",
|
|
21
|
+
"parameters": { "type": "object", "properties": { "seconds": { "type": "number" } }, "required": ["seconds"], "additionalProperties": false },
|
|
22
|
+
"effect": "mutation",
|
|
23
|
+
"needsConfirm": false
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"context": [{ "kind": "screen", "scopes": [{ "path": "scene-s1", "label": "Intro", "kind": "scene" }] }],
|
|
27
|
+
"instructions": "You are editing a video project." // optional
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- `messages` is the whole transcript in `ChatMessage` shape (`types.ts`). The server maps it to its provider's
|
|
32
|
+
format and maps the answer back; it never executes a tool — `tools` is a schema catalogue, not an offer.
|
|
33
|
+
- `context` blocks are host vocabulary. The server forwards them to the model as data, framed as data.
|
|
34
|
+
- Everything is JSON-serializable by contract; a tool whose `result` is not is the tool's bug.
|
|
35
|
+
|
|
36
|
+
## Response
|
|
37
|
+
|
|
38
|
+
`200` with a single JSON object:
|
|
39
|
+
|
|
40
|
+
```jsonc
|
|
41
|
+
{
|
|
42
|
+
"text": "Trimmed. Anything else?", // optional
|
|
43
|
+
"toolCalls": [{ "id": "c2", "name": "renderProject", "args": {} }], // optional
|
|
44
|
+
"stop": "end" // "end" | "toolUse"; defaults from toolCalls
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Any non-2xx status is surfaced to the session as one error event and ends the turn; a string `message` or
|
|
49
|
+
`error` field in a JSON body is quoted in that event, and any other body is not interpreted.
|
|
50
|
+
|
|
51
|
+
## Streaming
|
|
52
|
+
|
|
53
|
+
The same endpoint may answer `text/event-stream` instead when the request's `accept` names it: one `RunnerEvent`
|
|
54
|
+
(`types.ts`) JSON object per SSE `data:` line, ending with the `done` event. A failure after the stream opened
|
|
55
|
+
travels as one `error` event — the status line is already gone by then. `httpRunner` sends
|
|
56
|
+
`accept: text/event-stream, application/json` and branches on the response's content type, so a server that never
|
|
57
|
+
streams keeps answering the single JSON object above unchanged.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { AgentRunner, RunnerEvent, ToolCallRequest } from "./types";
|
|
2
|
+
|
|
3
|
+
export interface HttpRunnerOptions {
|
|
4
|
+
url: string;
|
|
5
|
+
headers?: () => Record<string, string>;
|
|
6
|
+
/** Swap the transport — tests, a framework's own HTTP client, a custom fetch with credentials. */
|
|
7
|
+
fetcher?: typeof fetch;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface TurnAnswer {
|
|
11
|
+
text?: string;
|
|
12
|
+
toolCalls?: ToolCallRequest[];
|
|
13
|
+
stop?: "end" | "toolUse";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const eventTypes = new Set(["text", "toolCall", "done", "error"]);
|
|
17
|
+
|
|
18
|
+
const parseFrame = (frame: string): RunnerEvent | null => {
|
|
19
|
+
const data = frame
|
|
20
|
+
.split("\n")
|
|
21
|
+
.filter((line) => line.startsWith("data:"))
|
|
22
|
+
.map((line) => line.slice(5).trim())
|
|
23
|
+
.join("\n");
|
|
24
|
+
if (!data) return null;
|
|
25
|
+
const event = JSON.parse(data) as RunnerEvent;
|
|
26
|
+
return eventTypes.has((event as { type?: string }).type ?? "") ? event : null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A manual reader loop rather than for-await: ReadableStream async iteration is still missing from browsers the
|
|
31
|
+
* chat runs in. Breaking out early cancels the stream so the connection is released.
|
|
32
|
+
*/
|
|
33
|
+
async function* streamedEvents(body: ReadableStream<Uint8Array>, signal: AbortSignal): AsyncGenerator<RunnerEvent> {
|
|
34
|
+
const reader = body.getReader();
|
|
35
|
+
const decoder = new TextDecoder();
|
|
36
|
+
let buffer = "";
|
|
37
|
+
let ended = false;
|
|
38
|
+
try {
|
|
39
|
+
while (true) {
|
|
40
|
+
const { done, value } = await reader.read();
|
|
41
|
+
if (done) break;
|
|
42
|
+
buffer += decoder.decode(value, { stream: true });
|
|
43
|
+
let cut = buffer.indexOf("\n\n");
|
|
44
|
+
while (cut !== -1) {
|
|
45
|
+
const frame = buffer.slice(0, cut);
|
|
46
|
+
buffer = buffer.slice(cut + 2);
|
|
47
|
+
cut = buffer.indexOf("\n\n");
|
|
48
|
+
const event = parseFrame(frame);
|
|
49
|
+
if (!event) continue;
|
|
50
|
+
yield event;
|
|
51
|
+
if (event.type === "done" || event.type === "error") {
|
|
52
|
+
ended = true;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
ended = true;
|
|
59
|
+
if (!signal.aborted) yield { type: "error", message: error instanceof Error ? error.message : String(error) };
|
|
60
|
+
return;
|
|
61
|
+
} finally {
|
|
62
|
+
void reader.cancel().catch(() => undefined);
|
|
63
|
+
}
|
|
64
|
+
if (!ended) yield { type: "error", message: "The turn stream ended without a done event." };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const turnErrorMessage = async (response: Response): Promise<string> => {
|
|
68
|
+
const fallback = `Agent turn failed: ${response.status}`;
|
|
69
|
+
try {
|
|
70
|
+
const body = (await response.json()) as { message?: unknown; error?: unknown };
|
|
71
|
+
const message = typeof body.message === "string" ? body.message : typeof body.error === "string" ? body.error : "";
|
|
72
|
+
return message ? `Agent turn failed: ${message}` : fallback;
|
|
73
|
+
} catch {
|
|
74
|
+
return fallback;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The default runner: one stateless POST per assistant turn, negotiated via `accept`. A server that streams
|
|
80
|
+
* answers `text/event-stream` — one `RunnerEvent` JSON per SSE `data:` line, ending with `done` — and one that
|
|
81
|
+
* does not answers the single JSON `TurnAnswer`; both ride the same endpoint and the same request JSON.
|
|
82
|
+
*
|
|
83
|
+
* The wire is documented in WIRE.md so any backend can serve it.
|
|
84
|
+
*/
|
|
85
|
+
export const httpRunner = ({ url, headers, fetcher }: HttpRunnerOptions): AgentRunner => ({
|
|
86
|
+
async *run(request) {
|
|
87
|
+
const response = await (fetcher ?? fetch)(url, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: {
|
|
90
|
+
"content-type": "application/json",
|
|
91
|
+
accept: "text/event-stream, application/json",
|
|
92
|
+
...(headers?.() ?? {}),
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify({
|
|
95
|
+
messages: request.messages,
|
|
96
|
+
tools: request.tools,
|
|
97
|
+
context: request.context,
|
|
98
|
+
...(request.instructions ? { instructions: request.instructions } : {}),
|
|
99
|
+
}),
|
|
100
|
+
signal: request.signal,
|
|
101
|
+
});
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
yield { type: "error", message: await turnErrorMessage(response) };
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (response.headers.get("content-type")?.includes("text/event-stream") && response.body) {
|
|
107
|
+
yield* streamedEvents(response.body, request.signal);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const turn = (await response.json()) as TurnAnswer;
|
|
111
|
+
if (turn.text) yield { type: "text", delta: turn.text };
|
|
112
|
+
for (const call of turn.toolCalls ?? []) yield { type: "toolCall", id: call.id, name: call.name, args: call.args };
|
|
113
|
+
yield { type: "done", stop: turn.stop ?? (turn.toolCalls?.length ? "toolUse" : "end") };
|
|
114
|
+
},
|
|
115
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
export * from "./Agentic";
|
|
3
|
+
export * from "./AgenticSurface";
|
|
4
|
+
export * from "./AgentProvider";
|
|
5
|
+
export * from "./AgentScope";
|
|
6
|
+
export * from "./AgentSession";
|
|
7
|
+
export * from "./httpRunner";
|
|
8
|
+
export * from "./surfaceContext";
|
|
9
|
+
export * from "./types";
|
|
10
|
+
export * from "./useAgent";
|
|
11
|
+
export * from "./useAgentGuide";
|
|
12
|
+
export * from "./useAgentResource";
|
|
13
|
+
export * from "./useAgentState";
|
|
14
|
+
export * from "./useAgentTool";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
import { AgenticSurface } from "./AgenticSurface";
|
|
4
|
+
|
|
5
|
+
export const SurfaceContext = createContext<AgenticSurface | null>(null);
|
|
6
|
+
export const ScopeContext = createContext<string[]>([]);
|
|
7
|
+
|
|
8
|
+
export const useSurface = () => useContext(SurfaceContext) ?? AgenticSurface.shared;
|
|
9
|
+
export const useScopePath = () => useContext(ScopeContext);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export type JsonSchema = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
export type ToolEffect = "state" | "query" | "mutation";
|
|
4
|
+
|
|
5
|
+
/** `true` asks with a default message, a string is the message, a function decides from the arguments. */
|
|
6
|
+
export type ToolConfirm = boolean | string | ((args: Record<string, unknown>) => string | boolean);
|
|
7
|
+
|
|
8
|
+
/** Re-checked at the moment of execution; a string is the refusal reason the agent reads. */
|
|
9
|
+
export type ToolGuard = (args: Record<string, unknown>) => true | string;
|
|
10
|
+
|
|
11
|
+
export interface ToolEntry {
|
|
12
|
+
name: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
parameters?: JsonSchema;
|
|
15
|
+
effect?: ToolEffect;
|
|
16
|
+
confirm?: ToolConfirm;
|
|
17
|
+
guard?: ToolGuard;
|
|
18
|
+
run: (args: Record<string, unknown>) => unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ResourceEntry {
|
|
22
|
+
name: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
/** `false` keeps it out of post-call diff reports — for values that change on their own every second. */
|
|
25
|
+
report?: boolean;
|
|
26
|
+
read: () => unknown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ScopeEntry {
|
|
30
|
+
id: string;
|
|
31
|
+
label?: string;
|
|
32
|
+
kind?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A bulk contributor of entries whose names are already full — how a host store joins the surface. `view` is the
|
|
37
|
+
* scope path a zone session reads through; a source that ignores it contributes the same entries to every view.
|
|
38
|
+
*/
|
|
39
|
+
export interface SurfaceSource {
|
|
40
|
+
tools?: (view?: string[]) => ToolEntry[];
|
|
41
|
+
resources?: (view?: string[]) => ResourceEntry[];
|
|
42
|
+
subscribe?: (listener: () => void) => () => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The reading half of a surface — what a session consumes. `AgenticSurface` is one; `surface.view(path)` answers a
|
|
47
|
+
* zone-scoped one over the same registry, so zones are views of the screen, never walls between its parts.
|
|
48
|
+
*/
|
|
49
|
+
export interface SurfaceView {
|
|
50
|
+
snapshot(): SurfaceSnapshot;
|
|
51
|
+
tool(name: string): ToolEntry | null;
|
|
52
|
+
call(name: string, args?: Record<string, unknown>): Promise<unknown>;
|
|
53
|
+
read(name: string): unknown;
|
|
54
|
+
diffSince(before: SurfaceSnapshot): ResourceDiff[];
|
|
55
|
+
subscribe(listener: () => void): () => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface PublishedTool {
|
|
59
|
+
name: string;
|
|
60
|
+
description?: string;
|
|
61
|
+
parameters?: JsonSchema;
|
|
62
|
+
effect?: ToolEffect;
|
|
63
|
+
needsConfirm: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PublishedResource {
|
|
67
|
+
name: string;
|
|
68
|
+
description?: string;
|
|
69
|
+
value?: unknown;
|
|
70
|
+
error?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface PublishedScope {
|
|
74
|
+
path: string;
|
|
75
|
+
label?: string;
|
|
76
|
+
kind?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface SurfaceSnapshot {
|
|
80
|
+
tools: PublishedTool[];
|
|
81
|
+
resources: PublishedResource[];
|
|
82
|
+
scopes: PublishedScope[];
|
|
83
|
+
/** Standing guidance texts, in registration order. Folded into the turn's instructions, not into context. */
|
|
84
|
+
guides: string[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ResourceDiff {
|
|
88
|
+
name: string;
|
|
89
|
+
value?: unknown;
|
|
90
|
+
error?: string;
|
|
91
|
+
removed?: boolean;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type ChatRole = "user" | "assistant" | "tool";
|
|
95
|
+
|
|
96
|
+
export interface ToolCallRequest {
|
|
97
|
+
id: string;
|
|
98
|
+
name: string;
|
|
99
|
+
args: Record<string, unknown>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ToolCallResult {
|
|
103
|
+
id: string;
|
|
104
|
+
name: string;
|
|
105
|
+
/** What `run` returned. Must be JSON-serializable — it rides the wire back to the model. */
|
|
106
|
+
result?: unknown;
|
|
107
|
+
/** What the call changed on the surface — the whole report for a tool that returns nothing. */
|
|
108
|
+
changes?: ResourceDiff[];
|
|
109
|
+
error?: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface ChatMessage {
|
|
113
|
+
role: ChatRole;
|
|
114
|
+
text?: string;
|
|
115
|
+
toolCalls?: ToolCallRequest[];
|
|
116
|
+
toolResults?: ToolCallResult[];
|
|
117
|
+
/** A failed or capped turn, recorded in the transcript rather than thrown past it. */
|
|
118
|
+
error?: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** One block of screen context the host assembles per turn. `kind` is the host's vocabulary; the wire forwards it verbatim. */
|
|
122
|
+
export interface ContextBlock {
|
|
123
|
+
kind: string;
|
|
124
|
+
[key: string]: unknown;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export type RunnerEvent =
|
|
128
|
+
| { type: "text"; delta: string }
|
|
129
|
+
| { type: "toolCall"; id: string; name: string; args: Record<string, unknown> }
|
|
130
|
+
| { type: "done"; stop: "end" | "toolUse" }
|
|
131
|
+
| { type: "error"; message: string };
|
|
132
|
+
|
|
133
|
+
export interface RunnerRequest {
|
|
134
|
+
messages: ChatMessage[];
|
|
135
|
+
tools: PublishedTool[];
|
|
136
|
+
context: ContextBlock[];
|
|
137
|
+
instructions?: string;
|
|
138
|
+
signal: AbortSignal;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** One model turn: request in, streamed events out. Where the loop runs is the implementation's business. */
|
|
142
|
+
export interface AgentRunner {
|
|
143
|
+
run: (request: RunnerRequest) => AsyncIterable<RunnerEvent>;
|
|
144
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, useContext, useSyncExternalStore } from "react";
|
|
3
|
+
import type { AgentSession } from "./AgentSession";
|
|
4
|
+
|
|
5
|
+
export const SessionContext = createContext<AgentSession | null>(null);
|
|
6
|
+
|
|
7
|
+
/** The enclosing session, re-rendering on every session change. `send`/`abort` are safe to destructure. */
|
|
8
|
+
export const useAgent = (): AgentSession => {
|
|
9
|
+
const session = useContext(SessionContext);
|
|
10
|
+
if (!session) throw new Error("useAgent needs an AgentProvider with a session or a runner.");
|
|
11
|
+
useSyncExternalStore(
|
|
12
|
+
session.subscribe,
|
|
13
|
+
() => session.version,
|
|
14
|
+
() => session.version,
|
|
15
|
+
);
|
|
16
|
+
return session;
|
|
17
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { useScopePath, useSurface } from "./surfaceContext";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Registers standing guidance for the agent while the component is mounted. Guidance is folded into the turn's
|
|
7
|
+
* instructions — it directs the model, unlike context, which is framed as data. The text is captured at mount
|
|
8
|
+
* like every declaration on the surface; remount to change it.
|
|
9
|
+
*/
|
|
10
|
+
export const useAgentGuide = (text: string): void => {
|
|
11
|
+
const surface = useSurface();
|
|
12
|
+
const scope = useScopePath();
|
|
13
|
+
const captured = useRef(text);
|
|
14
|
+
const scopeKey = scope.join(".");
|
|
15
|
+
useEffect(() => surface.registerGuide(scope, captured.current), [surface, scopeKey]);
|
|
16
|
+
};
|