@contractspec/module.ai-chat 4.3.6 → 4.3.7
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.md +62 -370
- package/dist/adapters/ai-sdk-bundle-adapter.d.ts +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/browser/context/index.js +93 -93
- package/dist/browser/core/index.js +308 -307
- package/dist/browser/index.js +3803 -3801
- package/dist/browser/presentation/components/index.js +2617 -2618
- package/dist/browser/presentation/hooks/index.js +476 -476
- package/dist/browser/presentation/index.js +2474 -2475
- package/dist/browser/providers/index.js +7 -7
- package/dist/context/index.d.ts +1 -1
- package/dist/context/index.js +93 -93
- package/dist/core/agent-tools-adapter.d.ts +1 -1
- package/dist/core/chat-service.d.ts +4 -4
- package/dist/core/create-chat-route.d.ts +1 -1
- package/dist/core/create-completion-route.d.ts +15 -0
- package/dist/core/export-formatters.d.ts +1 -1
- package/dist/core/index.d.ts +6 -6
- package/dist/core/index.js +308 -307
- package/dist/core/local-storage-conversation-store.d.ts +1 -1
- package/dist/core/surface-planner-tools.d.ts +2 -2
- package/dist/core/workflow-tools.d.ts +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.js +3803 -3801
- package/dist/node/context/index.js +93 -93
- package/dist/node/core/index.js +308 -307
- package/dist/node/index.js +3803 -3801
- package/dist/node/presentation/components/index.js +2617 -2618
- package/dist/node/presentation/hooks/index.js +476 -476
- package/dist/node/presentation/index.js +2474 -2475
- package/dist/node/providers/index.js +7 -7
- package/dist/presentation/components/ChainOfThought.d.ts +1 -1
- package/dist/presentation/components/ChatExportToolbar.d.ts +1 -1
- package/dist/presentation/components/ChatSidebar.d.ts +1 -1
- package/dist/presentation/components/ChatWithExport.d.ts +1 -1
- package/dist/presentation/components/index.d.ts +11 -11
- package/dist/presentation/components/index.js +2617 -2618
- package/dist/presentation/hooks/index.d.ts +4 -4
- package/dist/presentation/hooks/index.js +476 -476
- package/dist/presentation/hooks/useChat.d.ts +6 -6
- package/dist/presentation/hooks/useConversations.d.ts +1 -1
- package/dist/presentation/hooks/useProviders.d.ts +1 -1
- package/dist/presentation/index.js +2474 -2475
- package/dist/providers/index.d.ts +2 -2
- package/dist/providers/index.js +7 -7
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -1,385 +1,77 @@
|
|
|
1
1
|
# @contractspec/module.ai-chat
|
|
2
2
|
|
|
3
|
-
Website: https://contractspec.io
|
|
3
|
+
Website: https://contractspec.io
|
|
4
4
|
|
|
5
|
+
**AI chat module with context, core runtime, presentation components, hooks, providers, and agent-aware workflows.**
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
## What It Provides
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
- Provides the packaged chat feature layer used by examples and higher-level applications.
|
|
10
|
+
- Supports MCP tools, provider integration, presentation rendering, forms, and agent-mode workflows.
|
|
11
|
+
- Acts as the main composition layer between the low-level agent runtime and user-facing chat UIs.
|
|
12
|
+
- `src/adapters/` contains runtime, provider, or environment-specific adapters.
|
|
13
|
+
- `src/docs/` contains docblocks and documentation-facing exports.
|
|
14
|
+
- `src/presentation/` contains presentation-layer components and renderers.
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
## Installation
|
|
11
17
|
|
|
12
|
-
|
|
18
|
+
`npm install @contractspec/module.ai-chat`
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
- **Three Provider Modes**: Local (Ollama), BYOK (Bring Your Own Key), Managed (API proxy)
|
|
16
|
-
- **Full Workspace Context**: Access specs, files, and codebase for context-aware assistance
|
|
17
|
-
- **Streaming Responses**: Real-time token streaming for responsive UX
|
|
18
|
-
- **Usage Tracking**: Integrated metering and cost tracking
|
|
19
|
-
- **UI Components**: React components for chat interfaces
|
|
20
|
-
- **Export**: Export conversations to Markdown, TXT, or JSON; select one or many messages for export
|
|
21
|
-
- **Conversation Management**: New conversation, history sidebar, fork, edit messages, organize with projects and tags
|
|
22
|
-
- **Thinking Levels**: Select reasoning depth (instant, thinking, extra thinking, max); maps to Anthropic budgetTokens and OpenAI reasoningEffort
|
|
23
|
-
- **Workflow Creation Tools**: Create and modify workflows conversationally via `create_workflow_extension`, `compose_workflow`, and `generate_workflow_spec_code` (requires `@contractspec/lib.workflow-composer`)
|
|
24
|
-
- **ModelSelector**: Dynamic model selection by task dimension (reasoning vs latency) when using `@contractspec/lib.ai-providers` ModelSelector
|
|
25
|
-
- **Contracts-Spec Context**: Expose agent, data-views, operations, forms, and presentations to the model via `contractsContext`; agent tools can be wired from `AgentToolConfig[]`
|
|
26
|
-
- **Surface-Runtime Integration**: Full support for `@contractspec/lib.surface-runtime` — pass `surfacePlanConfig` to enable `propose-patch` tool; chat can propose layout changes for user approval
|
|
27
|
-
- **Presentation/Form Rendering**: Pass `presentationRenderer` and `formRenderer` to `ChatWithSidebar`; tool results with `presentationKey` or `formKey` render via host-provided components
|
|
28
|
-
- **MCP Tools**: Pass `mcpServers` (from `@contractspec/lib.ai-agent`) to `useChat`; tools from MCP servers are merged into chat tools
|
|
29
|
-
- **Agent Mode**: Pass `agentMode: { agent }` with a `ChatAgentAdapter` (use `createChatAgentAdapter` to wrap `ContractSpecAgent`); chat uses the agent for generation instead of ChatService
|
|
20
|
+
or
|
|
30
21
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
This module aligns with `specs/contractspec_modules_bundle_spec_2026-03-08`. `useChat` and `ChatContainer` provide the assistant slot UI for bundle surfaces. `AiChatFeature` (key `ai-chat`, version `1.0.0`) matches `ModuleBundleSpec.requires`. The `tools` option on `UseChatOptions` is wired to `streamText`; use `requireApproval: true` for tools that need user confirmation (requires server route for full support).
|
|
34
|
-
|
|
35
|
-
## Related Packages
|
|
36
|
-
|
|
37
|
-
- `@contractspec/lib.ai-providers` — Shared provider abstraction (types, factory, validation), ModelSelector for dynamic model selection
|
|
38
|
-
- `@contractspec/lib.workflow-composer` — Workflow composition and validation (optional; required for workflow creation tools)
|
|
39
|
-
- `@contractspec/lib.ai-agent` — Agent orchestration and tool execution; `AgentToolConfig` for agent tools
|
|
40
|
-
- `@contractspec/lib.surface-runtime` — Bundle surfaces, planner tools, `AiSdkBundleAdapter`; full integration when used in PM workbench
|
|
41
|
-
|
|
42
|
-
## Providers
|
|
43
|
-
|
|
44
|
-
| Provider | Local | BYOK | Managed |
|
|
45
|
-
|----------|-------|------|---------|
|
|
46
|
-
| Ollama | ✅ | - | - |
|
|
47
|
-
| OpenAI | - | ✅ | ✅ |
|
|
48
|
-
| Anthropic | - | ✅ | ✅ |
|
|
49
|
-
| Mistral | - | ✅ | ✅ |
|
|
50
|
-
| Google Gemini | - | ✅ | ✅ |
|
|
22
|
+
`bun add @contractspec/module.ai-chat`
|
|
51
23
|
|
|
52
24
|
## Usage
|
|
53
25
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
import { createProvider } from '@contractspec/lib.ai-providers';
|
|
58
|
-
import { ChatService } from '@contractspec/module.ai-chat';
|
|
59
|
-
|
|
60
|
-
const provider = createProvider({
|
|
61
|
-
provider: 'openai',
|
|
62
|
-
apiKey: process.env.OPENAI_API_KEY,
|
|
63
|
-
model: 'gpt-4o',
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const chatService = new ChatService({ provider });
|
|
67
|
-
|
|
68
|
-
const response = await chatService.send({
|
|
69
|
-
content: 'Help me create a new API endpoint',
|
|
70
|
-
});
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### With Workspace Context
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
import { createProvider } from '@contractspec/lib.ai-providers';
|
|
77
|
-
import { ChatService, WorkspaceContext } from '@contractspec/module.ai-chat';
|
|
78
|
-
|
|
79
|
-
const context = await WorkspaceContext.fromPath('/path/to/project');
|
|
80
|
-
const provider = createProvider({ provider: 'anthropic', proxyUrl: '/api/chat' });
|
|
81
|
-
|
|
82
|
-
const chatService = new ChatService({ provider, context });
|
|
83
|
-
|
|
84
|
-
// The chat now has access to specs, files, and can suggest code changes
|
|
85
|
-
const response = await chatService.send({
|
|
86
|
-
content: 'Add validation to the user.create command',
|
|
87
|
-
});
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### React Components
|
|
91
|
-
|
|
92
|
-
**With export and message selection** (recommended):
|
|
93
|
-
|
|
94
|
-
```tsx
|
|
95
|
-
import { ChatWithExport, ChatInput, useChat } from '@contractspec/module.ai-chat';
|
|
96
|
-
|
|
97
|
-
function VibeCodingChat() {
|
|
98
|
-
const { messages, conversation, sendMessage, isLoading } = useChat({
|
|
99
|
-
provider: 'openai',
|
|
100
|
-
mode: 'managed',
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
return (
|
|
104
|
-
<ChatWithExport messages={messages} conversation={conversation}>
|
|
105
|
-
<ChatInput onSend={sendMessage} disabled={isLoading} />
|
|
106
|
-
</ChatWithExport>
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
`ChatWithExport` provides an export toolbar (Markdown, TXT, JSON, copy to clipboard) and message selection checkboxes. Select messages to export only those, or export all when none are selected.
|
|
112
|
-
|
|
113
|
-
**With sidebar (history, new, fork, edit, project/tags)**:
|
|
114
|
-
|
|
115
|
-
```tsx
|
|
116
|
-
import { ChatWithSidebar } from '@contractspec/module.ai-chat';
|
|
117
|
-
|
|
118
|
-
function FullChat() {
|
|
119
|
-
return (
|
|
120
|
-
<ChatWithSidebar
|
|
121
|
-
systemPrompt="You are a helpful assistant."
|
|
122
|
-
/>
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
`ChatWithSidebar` includes a conversation history sidebar (LocalStorage-persisted), New/Fork buttons, message edit, project/tags organization, and a **Thinking Level** picker (instant, thinking, extra thinking, max). Uses `createLocalStorageConversationStore()` by default.
|
|
128
|
-
|
|
129
|
-
**Basic (no export)**:
|
|
130
|
-
|
|
131
|
-
```tsx
|
|
132
|
-
import { ChatContainer, ChatMessage, ChatInput } from '@contractspec/module.ai-chat/presentation/components';
|
|
133
|
-
import { useChat } from '@contractspec/module.ai-chat/presentation/hooks';
|
|
134
|
-
|
|
135
|
-
function BasicChat() {
|
|
136
|
-
const { messages, sendMessage, isLoading } = useChat();
|
|
137
|
-
|
|
138
|
-
return (
|
|
139
|
-
<ChatContainer>
|
|
140
|
-
{messages.map((msg) => <ChatMessage key={msg.id} message={msg} />)}
|
|
141
|
-
<ChatInput onSend={sendMessage} disabled={isLoading} />
|
|
142
|
-
</ChatContainer>
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### AI SDK Parity
|
|
148
|
-
|
|
149
|
-
This module aligns with the [Vercel AI SDK](https://sdk.vercel.ai) and AI Elements feature set:
|
|
150
|
-
|
|
151
|
-
- **fullStream**: Reasoning, tools, and sources from `streamText` fullStream
|
|
152
|
-
- **Thinking Levels**: `thinkingLevel` option (instant, thinking, extra_thinking, max) maps to Anthropic `budgetTokens` and OpenAI `reasoningEffort`; `ThinkingLevelPicker` in `ChatWithSidebar`
|
|
153
|
-
- **Tools**: Pass `tools` to `ChatServiceConfig` or `useChat`; supports `requireApproval` for approval workflow; optional `workflowToolsConfig` adds workflow creation tools
|
|
154
|
-
- **Message parts**: `ChatMessage` renders reasoning (collapsible), sources (citations), and tool invocations
|
|
155
|
-
- **Markdown**: Inline links and code blocks in message content
|
|
156
|
-
- **Export**: `ChatWithExport` and `ChatExportToolbar` support Markdown (.md), Plain Text (.txt), and JSON (.json); select messages for partial export or export all
|
|
157
|
-
- **Conversation Management**: `ChatWithSidebar` provides history, new conversation, fork, edit messages; `useChat` exposes `createNewConversation`, `editMessage`, `forkConversation`, `updateConversation`; `useConversations` for listing; `createLocalStorageConversationStore()` for persistence
|
|
158
|
-
|
|
159
|
-
### Server Route (Full AI SDK + Tool Approval)
|
|
160
|
-
|
|
161
|
-
For full AI SDK compatibility including tool approval, use `createChatRoute` with `@ai-sdk/react` useChat:
|
|
162
|
-
|
|
163
|
-
```ts
|
|
164
|
-
// app/api/chat/route.ts (Next.js App Router)
|
|
165
|
-
import {
|
|
166
|
-
createChatRoute,
|
|
167
|
-
CHAT_ROUTE_MAX_DURATION,
|
|
168
|
-
} from '@contractspec/module.ai-chat/core';
|
|
169
|
-
import { createProvider } from '@contractspec/lib.ai-providers';
|
|
170
|
-
|
|
171
|
-
const provider = createProvider({
|
|
172
|
-
provider: 'openai',
|
|
173
|
-
apiKey: process.env.OPENAI_API_KEY,
|
|
174
|
-
model: 'gpt-4o',
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
export const POST = createChatRoute({ provider });
|
|
178
|
-
export const maxDuration = CHAT_ROUTE_MAX_DURATION;
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
`maxDuration` is required for long-running streaming on Vercel/serverless. The route sends sources and reasoning by default (AI Elements parity). Override with `sendSources` and `sendReasoning` in options. Use `getModel` to support a model picker from the request body.
|
|
182
|
-
|
|
183
|
-
```tsx
|
|
184
|
-
// Client: use @ai-sdk/react useChat with DefaultChatTransport
|
|
185
|
-
import { useChat } from '@ai-sdk/react';
|
|
186
|
-
|
|
187
|
-
const { messages, sendMessage } = useChat({
|
|
188
|
-
api: '/api/chat',
|
|
189
|
-
});
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
The custom `useChat` from this module works with `ChatService` for simple deployments (no tools, no approval). For tools with `requireApproval`, use the server route pattern above.
|
|
193
|
-
|
|
194
|
-
### Workflow Creation Tools
|
|
195
|
-
|
|
196
|
-
When `workflowToolsConfig` is provided, the chat can create and modify workflows via AI SDK tools:
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
import { ChatService, createWorkflowTools } from '@contractspec/module.ai-chat';
|
|
200
|
-
import { WorkflowComposer } from '@contractspec/lib.workflow-composer';
|
|
201
|
-
import { createProvider } from '@contractspec/lib.ai-providers';
|
|
202
|
-
|
|
203
|
-
const baseWorkflows = [/* your WorkflowSpec[] */];
|
|
204
|
-
const provider = createProvider({ provider: 'anthropic', model: 'claude-sonnet-4' });
|
|
205
|
-
|
|
206
|
-
const chatService = new ChatService({
|
|
207
|
-
provider,
|
|
208
|
-
workflowToolsConfig: {
|
|
209
|
-
baseWorkflows,
|
|
210
|
-
composer: new WorkflowComposer(),
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// User can say: "Add a legal review step after validate-invoice"
|
|
215
|
-
// The model will call create_workflow_extension, compose_workflow, generate_workflow_spec_code
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
Tools: `create_workflow_extension` (validate extensions), `compose_workflow` (apply extensions to base), `generate_workflow_spec_code` (output TypeScript). Export `createWorkflowTools(baseWorkflows, composer)` for manual wiring.
|
|
219
|
-
|
|
220
|
-
### ModelSelector (Dynamic Model Selection)
|
|
221
|
-
|
|
222
|
-
Use `modelSelector` to pick models by task dimension (e.g. reasoning vs latency):
|
|
223
|
-
|
|
224
|
-
```typescript
|
|
225
|
-
import { createModelSelector } from '@contractspec/lib.ai-providers';
|
|
226
|
-
import { ChatService } from '@contractspec/module.ai-chat';
|
|
227
|
-
|
|
228
|
-
const modelSelector = createModelSelector(/* ranking config */);
|
|
229
|
-
const chatService = new ChatService({
|
|
230
|
-
provider: createProvider({ /* ... */ }),
|
|
231
|
-
modelSelector,
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
// ChatService will call modelSelector.selectAndCreate({ taskDimension: 'reasoning' | 'latency' })
|
|
235
|
-
// based on thinking level when modelSelector is set
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### AI Elements-Style Components
|
|
239
|
-
|
|
240
|
-
The module includes native implementations of [Reasoning](https://elements.ai-sdk.dev/components/reasoning), [Sources](https://elements.ai-sdk.dev/components/sources), [Suggestion](https://elements.ai-sdk.dev/components/suggestion), and [Chain of Thought](https://elements.ai-sdk.dev/components/chain-of-thought) patterns. Pass `components` to `ChatMessage` or `ChatWithExport` to override with ai-elements when installed:
|
|
241
|
-
|
|
242
|
-
```tsx
|
|
243
|
-
import { ChatWithSidebar, type ChatMessageComponents } from '@contractspec/module.ai-chat';
|
|
244
|
-
// When using ai-elements: import { Reasoning, Sources, ... } from '@/components/ai-elements/...';
|
|
245
|
-
|
|
246
|
-
<ChatWithSidebar
|
|
247
|
-
components={{
|
|
248
|
-
Reasoning: AiElementsReasoning,
|
|
249
|
-
Sources: AiElementsSources,
|
|
250
|
-
SourcesTrigger: AiElementsSourcesTrigger,
|
|
251
|
-
Source: AiElementsSource,
|
|
252
|
-
ChainOfThought: AiElementsChainOfThought,
|
|
253
|
-
ChainOfThoughtStep: AiElementsChainOfThoughtStep,
|
|
254
|
-
}}
|
|
255
|
-
suggestions={['Explain how X works', 'What is Y?']}
|
|
256
|
-
showSuggestionsWhenEmpty
|
|
257
|
-
/>
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
Use `suggestions` and `onSuggestionClick` for clickable prompt chips. Apps can optionally use [AI Elements](https://elements.ai-sdk.dev) for enhanced UI; provide an adapter from `ChatMessage` to `UIMessage` when integrating.
|
|
261
|
-
|
|
262
|
-
### useCompletion (Non-Chat Completion)
|
|
263
|
-
|
|
264
|
-
For inline suggestions, single-prompt completion, or other non-conversational use cases:
|
|
265
|
-
|
|
266
|
-
```tsx
|
|
267
|
-
import { useCompletion } from '@contractspec/module.ai-chat/presentation/hooks';
|
|
268
|
-
// or: import { useCompletion } from '@ai-sdk/react';
|
|
269
|
-
|
|
270
|
-
const { completion, complete, isLoading } = useCompletion({
|
|
271
|
-
api: '/api/completion',
|
|
272
|
-
});
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
Use `createCompletionRoute` for the API endpoint (see `createChatRoute` pattern).
|
|
276
|
-
|
|
277
|
-
### Contracts-Spec Context and Surface-Runtime
|
|
278
|
-
|
|
279
|
-
Pass `contractsContext` to expose agent, data-views, operations, forms, and presentations to the model. Pass `surfacePlanConfig` when embedding chat in a surface-runtime (e.g. PM workbench) to enable the `propose-patch` tool:
|
|
280
|
-
|
|
281
|
-
```tsx
|
|
282
|
-
import { ChatWithSidebar } from '@contractspec/module.ai-chat';
|
|
283
|
-
import type { ResolvedSurfacePlan } from '@contractspec/lib.surface-runtime/runtime/resolve-bundle';
|
|
284
|
-
|
|
285
|
-
function PmWorkbench({ plan }: { plan: ResolvedSurfacePlan }) {
|
|
286
|
-
const [currentPlan, setPlan] = useState(plan);
|
|
287
|
-
const onPatchProposal = useCallback((proposal) => {
|
|
288
|
-
setPlan(prev => ({
|
|
289
|
-
...prev,
|
|
290
|
-
ai: { ...prev.ai, proposals: [...(prev.ai?.proposals ?? []), proposal] },
|
|
291
|
-
}));
|
|
292
|
-
}, []);
|
|
293
|
-
|
|
294
|
-
return (
|
|
295
|
-
<ChatWithSidebar
|
|
296
|
-
surfacePlanConfig={{ plan: currentPlan, onPatchProposal }}
|
|
297
|
-
systemPrompt="You are a PM workbench assistant. Propose layout changes when helpful."
|
|
298
|
-
/>
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
`createAiSdkBundleAdapter` from `@contractspec/module.ai-chat/adapters` implements `AiSdkBundleAdapter` for surface-runtime planner integration.
|
|
304
|
-
|
|
305
|
-
### streamObject / generateObject
|
|
306
|
-
|
|
307
|
-
For structured output (schema-driven generation), use the AI SDK directly: `streamObject` and `generateObject` from `ai`. This module focuses on chat; add `useObject` or equivalent in a separate module when needed.
|
|
308
|
-
|
|
309
|
-
### Voice / Speech
|
|
310
|
-
|
|
311
|
-
Speech Input, Transcription, Voice Selector, and related UI are planned as a separate submodule or feature flag. Track via roadmap.
|
|
26
|
+
Import the root entrypoint from `@contractspec/module.ai-chat`, or choose a documented subpath when you only need one part of the package surface.
|
|
312
27
|
|
|
313
28
|
## Architecture
|
|
314
29
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
## Metrics
|
|
365
|
-
|
|
366
|
-
The module tracks the following metrics via `@contractspec/lib.metering`:
|
|
367
|
-
|
|
368
|
-
| Metric | Description |
|
|
369
|
-
|--------|-------------|
|
|
370
|
-
| `ai_chat.messages` | Total chat messages sent |
|
|
371
|
-
| `ai_chat.tokens_input` | Input tokens consumed |
|
|
372
|
-
| `ai_chat.tokens_output` | Output tokens generated |
|
|
373
|
-
| `ai_chat.latency_ms` | Response latency |
|
|
374
|
-
| `ai_chat.errors` | Failed completions |
|
|
375
|
-
|
|
376
|
-
## Feature Flags
|
|
377
|
-
|
|
378
|
-
- `ai_chat.enabled` - Master toggle for the chat feature
|
|
379
|
-
- `ai_chat.managed_keys` - Enable managed key mode (API proxy)
|
|
380
|
-
- `ai_chat.workspace_context` - Enable workspace read/write access
|
|
381
|
-
- `ai_chat.code_execution` - Enable code execution (future)
|
|
382
|
-
|
|
383
|
-
## License
|
|
384
|
-
|
|
385
|
-
MIT
|
|
30
|
+
- `src/context/` contains shared chat providers and contextual runtime state.
|
|
31
|
+
- `src/core/` contains chat orchestration, workflows, and non-UI runtime logic.
|
|
32
|
+
- `src/presentation/` exports UI components and React hooks for embedding the chat experience.
|
|
33
|
+
- `src/providers/` exposes provider bindings and provider-facing integration helpers.
|
|
34
|
+
- Top-level feature, capability, operation, schema, and event files define the module contract surface.
|
|
35
|
+
- `src/ai-chat.capability.ts` defines a capability surface.
|
|
36
|
+
|
|
37
|
+
## Public Entry Points
|
|
38
|
+
|
|
39
|
+
- Exports the root module plus context, core, presentation, presentation/components, presentation/hooks, and providers subpaths.
|
|
40
|
+
- Export `.` resolves through `./src/index.ts`.
|
|
41
|
+
- Export `./context` resolves through `./src/context/index.ts`.
|
|
42
|
+
- Export `./core` resolves through `./src/core/index.ts`.
|
|
43
|
+
- Export `./presentation` resolves through `./src/presentation/index.ts`.
|
|
44
|
+
- Export `./presentation/components` resolves through `./src/presentation/components/index.ts`.
|
|
45
|
+
- Export `./presentation/hooks` resolves through `./src/presentation/hooks/index.ts`.
|
|
46
|
+
- Export `./providers` resolves through `./src/providers/index.ts`.
|
|
47
|
+
|
|
48
|
+
## Local Commands
|
|
49
|
+
|
|
50
|
+
- `bun run dev` — contractspec-bun-build dev
|
|
51
|
+
- `bun run build` — bun run prebuild && bun run build:bundle && bun run build:types
|
|
52
|
+
- `bun run test` — bun test
|
|
53
|
+
- `bun run lint` — bun lint:fix
|
|
54
|
+
- `bun run lint:check` — biome check .
|
|
55
|
+
- `bun run lint:fix` — biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .
|
|
56
|
+
- `bun run typecheck` — tsc --noEmit
|
|
57
|
+
- `bun run publish:pkg` — bun publish --tolerate-republish --ignore-scripts --verbose
|
|
58
|
+
- `bun run publish:pkg:canary` — bun publish:pkg --tag canary
|
|
59
|
+
- `bun run clean` — rimraf dist .turbo
|
|
60
|
+
- `bun run build:bundle` — contractspec-bun-build transpile
|
|
61
|
+
- `bun run build:types` — contractspec-bun-build types
|
|
62
|
+
- `bun run prebuild` — contractspec-bun-build prebuild
|
|
63
|
+
|
|
64
|
+
## Recent Updates
|
|
65
|
+
|
|
66
|
+
- Replace eslint+prettier by biomejs to optimize speed.
|
|
67
|
+
- Agentic workflows — subagents, memory tools, and next steps.
|
|
68
|
+
- Vnext ai-native.
|
|
69
|
+
- Backend operations + frontend rendering support.
|
|
70
|
+
- Use browser-safe MCP client stub in client bundles.
|
|
71
|
+
- Add changesets and apply pending fixes.
|
|
72
|
+
|
|
73
|
+
## Notes
|
|
74
|
+
|
|
75
|
+
- Depends on `lib.ai-agent`, `lib.ai-providers`, `lib.contracts-spec`, `lib.schema`, `lib.metering`, `lib.cost-tracking`, `lib.surface-runtime`.
|
|
76
|
+
- React peer dependency (>=19.2.4); changes here affect all chat surfaces.
|
|
77
|
+
- Metering and cost-tracking are wired in -- never bypass them.
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* AiSdkBundleAdapter implementation using ChatService.
|
|
3
3
|
* Enables surface-runtime to drive chat for planner/requestPatches flows.
|
|
4
4
|
*/
|
|
5
|
+
import type { Provider as ChatProvider } from '@contractspec/lib.ai-providers';
|
|
5
6
|
import type { AiSdkBundleAdapter } from '@contractspec/lib.surface-runtime/adapters/interfaces';
|
|
6
7
|
import type { SurfacePatchProposal } from '@contractspec/lib.surface-runtime/spec/types';
|
|
7
|
-
import type { Provider as ChatProvider } from '@contractspec/lib.ai-providers';
|
|
8
8
|
export interface CreateAiSdkBundleAdapterDeps {
|
|
9
9
|
/** Provider for creating ChatService (from createProvider) */
|
|
10
10
|
provider: ChatProvider;
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Adapters for integrating ai-chat with external runtimes.
|
|
3
3
|
*/
|
|
4
|
-
export {
|
|
4
|
+
export { type CreateAiSdkBundleAdapterDeps, createAiSdkBundleAdapter, } from './ai-sdk-bundle-adapter';
|
|
@@ -6,99 +6,6 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
6
6
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
// src/context/workspace-context.ts
|
|
10
|
-
class WorkspaceContext {
|
|
11
|
-
workspacePath;
|
|
12
|
-
allowWrites;
|
|
13
|
-
specs = [];
|
|
14
|
-
files = [];
|
|
15
|
-
initialized = false;
|
|
16
|
-
constructor(config) {
|
|
17
|
-
this.workspacePath = config.workspacePath;
|
|
18
|
-
this.allowWrites = config.allowWrites ?? false;
|
|
19
|
-
}
|
|
20
|
-
async initialize() {
|
|
21
|
-
if (this.initialized)
|
|
22
|
-
return;
|
|
23
|
-
this.initialized = true;
|
|
24
|
-
}
|
|
25
|
-
getSpecs() {
|
|
26
|
-
return this.specs;
|
|
27
|
-
}
|
|
28
|
-
getFiles() {
|
|
29
|
-
return this.files;
|
|
30
|
-
}
|
|
31
|
-
addSpecs(specs) {
|
|
32
|
-
this.specs.push(...specs);
|
|
33
|
-
}
|
|
34
|
-
addFiles(files) {
|
|
35
|
-
this.files.push(...files);
|
|
36
|
-
}
|
|
37
|
-
getSummary() {
|
|
38
|
-
const commands = this.specs.filter((s) => s.type === "command").length;
|
|
39
|
-
const queries = this.specs.filter((s) => s.type === "query").length;
|
|
40
|
-
const events = this.specs.filter((s) => s.type === "event").length;
|
|
41
|
-
const presentations = this.specs.filter((s) => s.type === "presentation").length;
|
|
42
|
-
const tsFiles = this.files.filter((f) => f.extension === ".ts").length;
|
|
43
|
-
const specFiles = this.files.filter((f) => f.isSpec).length;
|
|
44
|
-
return {
|
|
45
|
-
name: this.workspacePath.split("/").pop() ?? "workspace",
|
|
46
|
-
path: this.workspacePath,
|
|
47
|
-
specs: {
|
|
48
|
-
total: this.specs.length,
|
|
49
|
-
commands,
|
|
50
|
-
queries,
|
|
51
|
-
events,
|
|
52
|
-
presentations
|
|
53
|
-
},
|
|
54
|
-
files: {
|
|
55
|
-
total: this.files.length,
|
|
56
|
-
typescript: tsFiles,
|
|
57
|
-
specFiles
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
getContextSummary() {
|
|
62
|
-
const summary = this.getSummary();
|
|
63
|
-
const parts = [
|
|
64
|
-
`Workspace: ${summary.name}`,
|
|
65
|
-
`Path: ${summary.path}`,
|
|
66
|
-
"",
|
|
67
|
-
"### Specs",
|
|
68
|
-
`- Commands: ${summary.specs.commands}`,
|
|
69
|
-
`- Queries: ${summary.specs.queries}`,
|
|
70
|
-
`- Events: ${summary.specs.events}`,
|
|
71
|
-
`- Presentations: ${summary.specs.presentations}`
|
|
72
|
-
];
|
|
73
|
-
if (this.specs.length > 0) {
|
|
74
|
-
parts.push("", "### Available Specs");
|
|
75
|
-
for (const spec of this.specs.slice(0, 20)) {
|
|
76
|
-
parts.push(`- ${spec.name} (${spec.type})`);
|
|
77
|
-
}
|
|
78
|
-
if (this.specs.length > 20) {
|
|
79
|
-
parts.push(`- ... and ${this.specs.length - 20} more`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return parts.join(`
|
|
83
|
-
`);
|
|
84
|
-
}
|
|
85
|
-
findSpecs(query) {
|
|
86
|
-
const lowerQuery = query.toLowerCase();
|
|
87
|
-
return this.specs.filter((s) => s.name.toLowerCase().includes(lowerQuery) || s.description?.toLowerCase().includes(lowerQuery) || s.tags?.some((t) => t.toLowerCase().includes(lowerQuery)));
|
|
88
|
-
}
|
|
89
|
-
findFiles(query) {
|
|
90
|
-
const lowerQuery = query.toLowerCase();
|
|
91
|
-
return this.files.filter((f) => f.path.toLowerCase().includes(lowerQuery) || f.name.toLowerCase().includes(lowerQuery));
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
async function createWorkspaceContext(path, options) {
|
|
95
|
-
const context = new WorkspaceContext({
|
|
96
|
-
workspacePath: path,
|
|
97
|
-
...options
|
|
98
|
-
});
|
|
99
|
-
await context.initialize();
|
|
100
|
-
return context;
|
|
101
|
-
}
|
|
102
9
|
// src/context/context-builder.ts
|
|
103
10
|
function estimateTokens(text) {
|
|
104
11
|
return Math.ceil(text.length / 4);
|
|
@@ -405,6 +312,99 @@ function createNodeFileOperations(workspacePath, allowWrites = false) {
|
|
|
405
312
|
};
|
|
406
313
|
return new FileOperations(fs, workspacePath, allowWrites);
|
|
407
314
|
}
|
|
315
|
+
// src/context/workspace-context.ts
|
|
316
|
+
class WorkspaceContext {
|
|
317
|
+
workspacePath;
|
|
318
|
+
allowWrites;
|
|
319
|
+
specs = [];
|
|
320
|
+
files = [];
|
|
321
|
+
initialized = false;
|
|
322
|
+
constructor(config) {
|
|
323
|
+
this.workspacePath = config.workspacePath;
|
|
324
|
+
this.allowWrites = config.allowWrites ?? false;
|
|
325
|
+
}
|
|
326
|
+
async initialize() {
|
|
327
|
+
if (this.initialized)
|
|
328
|
+
return;
|
|
329
|
+
this.initialized = true;
|
|
330
|
+
}
|
|
331
|
+
getSpecs() {
|
|
332
|
+
return this.specs;
|
|
333
|
+
}
|
|
334
|
+
getFiles() {
|
|
335
|
+
return this.files;
|
|
336
|
+
}
|
|
337
|
+
addSpecs(specs) {
|
|
338
|
+
this.specs.push(...specs);
|
|
339
|
+
}
|
|
340
|
+
addFiles(files) {
|
|
341
|
+
this.files.push(...files);
|
|
342
|
+
}
|
|
343
|
+
getSummary() {
|
|
344
|
+
const commands = this.specs.filter((s) => s.type === "command").length;
|
|
345
|
+
const queries = this.specs.filter((s) => s.type === "query").length;
|
|
346
|
+
const events = this.specs.filter((s) => s.type === "event").length;
|
|
347
|
+
const presentations = this.specs.filter((s) => s.type === "presentation").length;
|
|
348
|
+
const tsFiles = this.files.filter((f) => f.extension === ".ts").length;
|
|
349
|
+
const specFiles = this.files.filter((f) => f.isSpec).length;
|
|
350
|
+
return {
|
|
351
|
+
name: this.workspacePath.split("/").pop() ?? "workspace",
|
|
352
|
+
path: this.workspacePath,
|
|
353
|
+
specs: {
|
|
354
|
+
total: this.specs.length,
|
|
355
|
+
commands,
|
|
356
|
+
queries,
|
|
357
|
+
events,
|
|
358
|
+
presentations
|
|
359
|
+
},
|
|
360
|
+
files: {
|
|
361
|
+
total: this.files.length,
|
|
362
|
+
typescript: tsFiles,
|
|
363
|
+
specFiles
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
getContextSummary() {
|
|
368
|
+
const summary = this.getSummary();
|
|
369
|
+
const parts = [
|
|
370
|
+
`Workspace: ${summary.name}`,
|
|
371
|
+
`Path: ${summary.path}`,
|
|
372
|
+
"",
|
|
373
|
+
"### Specs",
|
|
374
|
+
`- Commands: ${summary.specs.commands}`,
|
|
375
|
+
`- Queries: ${summary.specs.queries}`,
|
|
376
|
+
`- Events: ${summary.specs.events}`,
|
|
377
|
+
`- Presentations: ${summary.specs.presentations}`
|
|
378
|
+
];
|
|
379
|
+
if (this.specs.length > 0) {
|
|
380
|
+
parts.push("", "### Available Specs");
|
|
381
|
+
for (const spec of this.specs.slice(0, 20)) {
|
|
382
|
+
parts.push(`- ${spec.name} (${spec.type})`);
|
|
383
|
+
}
|
|
384
|
+
if (this.specs.length > 20) {
|
|
385
|
+
parts.push(`- ... and ${this.specs.length - 20} more`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return parts.join(`
|
|
389
|
+
`);
|
|
390
|
+
}
|
|
391
|
+
findSpecs(query) {
|
|
392
|
+
const lowerQuery = query.toLowerCase();
|
|
393
|
+
return this.specs.filter((s) => s.name.toLowerCase().includes(lowerQuery) || s.description?.toLowerCase().includes(lowerQuery) || s.tags?.some((t) => t.toLowerCase().includes(lowerQuery)));
|
|
394
|
+
}
|
|
395
|
+
findFiles(query) {
|
|
396
|
+
const lowerQuery = query.toLowerCase();
|
|
397
|
+
return this.files.filter((f) => f.path.toLowerCase().includes(lowerQuery) || f.name.toLowerCase().includes(lowerQuery));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
async function createWorkspaceContext(path, options) {
|
|
401
|
+
const context = new WorkspaceContext({
|
|
402
|
+
workspacePath: path,
|
|
403
|
+
...options
|
|
404
|
+
});
|
|
405
|
+
await context.initialize();
|
|
406
|
+
return context;
|
|
407
|
+
}
|
|
408
408
|
export {
|
|
409
409
|
createWorkspaceContext,
|
|
410
410
|
createNodeFileOperations,
|