@ulam/halohalo 0.3.2 → 0.3.3
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 +212 -212
- package/agenticAiService.js +76 -76
- package/aiService.js +31 -31
- package/angular.js +102 -102
- package/connectivity.js +17 -17
- package/createCompletion.js +113 -113
- package/createProviderConfig.js +85 -85
- package/fetch.js +134 -134
- package/index.js +20 -20
- package/init.js +33 -33
- package/models.js +40 -48
- package/package.json +1 -1
- package/react.js +2 -2
- package/useCompletion.js +17 -17
- package/useProviderConfig.js +27 -27
- package/vue.js +73 -73
package/README.md
CHANGED
|
@@ -1,212 +1,212 @@
|
|
|
1
|
-
# @ulam/halohalo
|
|
2
|
-
|
|
3
|
-
AI service adapters, model configuration, and provider abstraction. Vanilla core with React, Vue, and Angular adapters.
|
|
4
|
-
|
|
5
|
-
Named for halo-halo, the Filipino shaved ice dessert: a mix of many things that somehow works together.
|
|
6
|
-
|
|
7
|
-
## Purpose & Scope
|
|
8
|
-
|
|
9
|
-
**What halohalo does:**
|
|
10
|
-
|
|
11
|
-
- Provider abstraction for Anthropic, OpenAI, and Google
|
|
12
|
-
- API key management (localStorage-backed, never sent to server)
|
|
13
|
-
- Model selection and configuration per provider
|
|
14
|
-
- Completion calls with consistent interface across providers
|
|
15
|
-
- Tool calling and agentic mode for complex operations
|
|
16
|
-
- Framework-agnostic vanilla core with framework adapters
|
|
17
|
-
- Zero build-time provider detection or configuration
|
|
18
|
-
|
|
19
|
-
**What halohalo doesn't do:**
|
|
20
|
-
|
|
21
|
-
- Message history or conversation management (bring your own state)
|
|
22
|
-
- Streaming response handling (returns complete results)
|
|
23
|
-
- Rate limiting or retry logic (use middleware patterns for these)
|
|
24
|
-
- Token counting or pricing calculation (external concerns)
|
|
25
|
-
- API key validation or rotation (user responsibility)
|
|
26
|
-
- Multi-user authentication or access control
|
|
27
|
-
|
|
28
|
-
**Who should use halohalo:**
|
|
29
|
-
|
|
30
|
-
- Apps that need multiple AI provider support with runtime switching
|
|
31
|
-
- Projects storing API keys client-side (browser-only, no backend)
|
|
32
|
-
- Vanilla JavaScript, React, Vue, or Angular apps using AI features
|
|
33
|
-
- Applications wanting provider-agnostic completion calls
|
|
34
|
-
- Teams integrating AI features without external API servers
|
|
35
|
-
|
|
36
|
-
## The ulam Framework
|
|
37
|
-
|
|
38
|
-
Halohalo is one of six independent packages in the ulam framework. See [docs/ARCHITECTURE.md](../../docs/ARCHITECTURE.md) for the complete framework structure and dependency graph.
|
|
39
|
-
|
|
40
|
-
## Install
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
npm install @ulam/halohalo
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Peer dependencies: `fuse.js >= 7` (for search). Framework adapters add `react >= 18`, `vue >= 3`, or `@angular/core >= 17` as needed.
|
|
47
|
-
|
|
48
|
-
## Supported providers
|
|
49
|
-
|
|
50
|
-
- Anthropic (Claude)
|
|
51
|
-
- OpenAI (GPT)
|
|
52
|
-
- Google (Gemini)
|
|
53
|
-
|
|
54
|
-
Bring your own API key. Keys stay in the browser via localStorage and are sent directly to the provider, never to a server.
|
|
55
|
-
|
|
56
|
-
## Usage
|
|
57
|
-
|
|
58
|
-
### Initialize
|
|
59
|
-
|
|
60
|
-
```js
|
|
61
|
-
import { initApiKeys, initModels, getAiProvider } from '@ulam/halohalo'
|
|
62
|
-
|
|
63
|
-
initApiKeys({ anthropic: 'sk-ant-...', openai: 'sk-...' })
|
|
64
|
-
initModels({ anthropic: 'claude-sonnet-4-6', openai: 'gpt-4o' })
|
|
65
|
-
|
|
66
|
-
const provider = getAiProvider() // 'anthropic' | 'openai' | 'google'
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Call a provider
|
|
70
|
-
|
|
71
|
-
```js
|
|
72
|
-
import { createCompletion } from '@ulam/halohalo'
|
|
73
|
-
|
|
74
|
-
const result = await createCompletion({
|
|
75
|
-
prompt: 'Rewrite this finding for a mobile context.',
|
|
76
|
-
systemPrompt: 'You are an accessibility audit assistant.',
|
|
77
|
-
})
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Anthropic with tool use
|
|
81
|
-
|
|
82
|
-
```js
|
|
83
|
-
import { callAnthropicWithTools } from '@ulam/halohalo'
|
|
84
|
-
|
|
85
|
-
const result = await callAnthropicWithTools({
|
|
86
|
-
messages,
|
|
87
|
-
tools,
|
|
88
|
-
system: 'You are an accessibility audit assistant.',
|
|
89
|
-
})
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### Agentic mode
|
|
93
|
-
|
|
94
|
-
Agentic mode uses tool calling to search the corpus for similar past revisions before rewriting, matching the tone and depth of established work.
|
|
95
|
-
|
|
96
|
-
```js
|
|
97
|
-
import { getAgenticRefinement } from '@ulam/halohalo'
|
|
98
|
-
|
|
99
|
-
const revised = await getAgenticRefinement({
|
|
100
|
-
finding,
|
|
101
|
-
notes: 'This is specific to mobile, element is a tooltip',
|
|
102
|
-
})
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### React hooks adapter
|
|
106
|
-
|
|
107
|
-
```jsx
|
|
108
|
-
import { useProviderConfig, useCompletion } from '@ulam/halohalo/react'
|
|
109
|
-
|
|
110
|
-
function AISettings() {
|
|
111
|
-
const { provider, model, setProvider } = useProviderConfig()
|
|
112
|
-
const { complete, loading } = useCompletion()
|
|
113
|
-
|
|
114
|
-
return (
|
|
115
|
-
<button onClick={() => complete('Rewrite this for mobile.')}>
|
|
116
|
-
{loading ? 'Revising...' : 'Rewrite'}
|
|
117
|
-
</button>
|
|
118
|
-
)
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### Vue composables adapter
|
|
123
|
-
|
|
124
|
-
`@ulam/halohalo/vue` provides composables that wrap the vanilla `createCompletion` and `createProviderConfig` with Vue reactivity. Loading and animating state are `readonly` refs that update as the completion runs.
|
|
125
|
-
|
|
126
|
-
```js
|
|
127
|
-
import { useCompletion, useProviderConfig } from '@ulam/halohalo/vue'
|
|
128
|
-
import { onUnmounted } from 'vue'
|
|
129
|
-
|
|
130
|
-
// In setup()
|
|
131
|
-
const { loading, animating, complete, cancel, cleanup } = useCompletion()
|
|
132
|
-
onUnmounted(cleanup)
|
|
133
|
-
|
|
134
|
-
// loading.value and animating.value are reactive
|
|
135
|
-
await complete({ prompt: 'Rewrite this for mobile.' })
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
`useProviderConfig()` returns reactive refs for `provider`, `models`, and `mode`, plus all setter functions from the vanilla config store.
|
|
139
|
-
|
|
140
|
-
```js
|
|
141
|
-
const { provider, setProvider } = useProviderConfig()
|
|
142
|
-
// provider.value is reactive
|
|
143
|
-
setProvider('openai')
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Both composables return a `cleanup` function. Call it in `onUnmounted()` if the composable is used inside a component. For app-level use outside a component, cleanup is optional.
|
|
147
|
-
|
|
148
|
-
### Angular services adapter
|
|
149
|
-
|
|
150
|
-
`@ulam/halohalo/angular` provides two injectable services backed by Angular signals.
|
|
151
|
-
|
|
152
|
-
**`CompletionService`** is not `providedIn: 'root'`. Each injection creates a separate completion instance with its own loading state. Provide it at the component level for scoped instances, or at the application level for a shared one.
|
|
153
|
-
|
|
154
|
-
```ts
|
|
155
|
-
import { CompletionService, ProviderConfigService, provideHalohalo } from '@ulam/halohalo/angular'
|
|
156
|
-
|
|
157
|
-
// Application root (shared singleton):
|
|
158
|
-
bootstrapApplication(AppComponent, {
|
|
159
|
-
providers: [provideHalohalo()]
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
// Or component-level (scoped per component):
|
|
163
|
-
@Component({
|
|
164
|
-
providers: [CompletionService],
|
|
165
|
-
template: `
|
|
166
|
-
<button (click)="rewrite()" [disabled]="completion.loading()">
|
|
167
|
-
{{ completion.loading() ? 'Revising...' : 'Rewrite' }}
|
|
168
|
-
</button>
|
|
169
|
-
`
|
|
170
|
-
})
|
|
171
|
-
export class RewriteButtonComponent {
|
|
172
|
-
completion = inject(CompletionService)
|
|
173
|
-
|
|
174
|
-
async rewrite() {
|
|
175
|
-
await this.completion.complete({ prompt: 'Rewrite for mobile.' })
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
`completion.loading()` and `completion.animating()` are Angular `computed` signals that work directly in templates and in `effect()` calls.
|
|
181
|
-
|
|
182
|
-
**`ProviderConfigService`** is `providedIn: 'root'`, giving you one config store for the whole app.
|
|
183
|
-
|
|
184
|
-
```ts
|
|
185
|
-
@Component({ ... })
|
|
186
|
-
export class SettingsComponent {
|
|
187
|
-
providerConfig = inject(ProviderConfigService)
|
|
188
|
-
|
|
189
|
-
// In template:
|
|
190
|
-
// {{ providerConfig.provider() }}
|
|
191
|
-
// (click)="providerConfig.setProvider('openai')"
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
## Design
|
|
196
|
-
|
|
197
|
-
**Bring your own key.** No proxy, no server, no account. API keys are stored in localStorage and sent directly to the provider.
|
|
198
|
-
|
|
199
|
-
**Provider-agnostic core.** `createCompletion` works the same regardless of which provider is active. Switch providers without changing call sites.
|
|
200
|
-
|
|
201
|
-
**Vanilla-first.** The core has no framework dependency. Import from `/react`, `/vue`, or `/angular` only when you need framework reactivity.
|
|
202
|
-
|
|
203
|
-
## Subpath exports
|
|
204
|
-
|
|
205
|
-
| Import | Contents |
|
|
206
|
-
| ------ | -------- |
|
|
207
|
-
| `@ulam/halohalo` | Vanilla core: `createCompletion`, `createProviderConfig`, `callProvider`, `callAnthropicWithTools`, `getAgenticRefinement`, `searchItems`, `makeSearchTool`, and more |
|
|
208
|
-
| `@ulam/halohalo/react` | `useCompletion`, `useProviderConfig` |
|
|
209
|
-
| `@ulam/halohalo/vue` | `useCompletion`, `useProviderConfig` |
|
|
210
|
-
| `@ulam/halohalo/angular` | `CompletionService`, `ProviderConfigService`, `provideHalohalo` |
|
|
211
|
-
|
|
212
|
-
See the [root README](../../README.md) for a complete framework support overview across all ulam packages.
|
|
1
|
+
# @ulam/halohalo
|
|
2
|
+
|
|
3
|
+
AI service adapters, model configuration, and provider abstraction. Vanilla core with React, Vue, and Angular adapters.
|
|
4
|
+
|
|
5
|
+
Named for halo-halo, the Filipino shaved ice dessert: a mix of many things that somehow works together.
|
|
6
|
+
|
|
7
|
+
## Purpose & Scope
|
|
8
|
+
|
|
9
|
+
**What halohalo does:**
|
|
10
|
+
|
|
11
|
+
- Provider abstraction for Anthropic, OpenAI, and Google
|
|
12
|
+
- API key management (localStorage-backed, never sent to server)
|
|
13
|
+
- Model selection and configuration per provider
|
|
14
|
+
- Completion calls with consistent interface across providers
|
|
15
|
+
- Tool calling and agentic mode for complex operations
|
|
16
|
+
- Framework-agnostic vanilla core with framework adapters
|
|
17
|
+
- Zero build-time provider detection or configuration
|
|
18
|
+
|
|
19
|
+
**What halohalo doesn't do:**
|
|
20
|
+
|
|
21
|
+
- Message history or conversation management (bring your own state)
|
|
22
|
+
- Streaming response handling (returns complete results)
|
|
23
|
+
- Rate limiting or retry logic (use middleware patterns for these)
|
|
24
|
+
- Token counting or pricing calculation (external concerns)
|
|
25
|
+
- API key validation or rotation (user responsibility)
|
|
26
|
+
- Multi-user authentication or access control
|
|
27
|
+
|
|
28
|
+
**Who should use halohalo:**
|
|
29
|
+
|
|
30
|
+
- Apps that need multiple AI provider support with runtime switching
|
|
31
|
+
- Projects storing API keys client-side (browser-only, no backend)
|
|
32
|
+
- Vanilla JavaScript, React, Vue, or Angular apps using AI features
|
|
33
|
+
- Applications wanting provider-agnostic completion calls
|
|
34
|
+
- Teams integrating AI features without external API servers
|
|
35
|
+
|
|
36
|
+
## The ulam Framework
|
|
37
|
+
|
|
38
|
+
Halohalo is one of six independent packages in the ulam framework. See [docs/ARCHITECTURE.md](../../docs/ARCHITECTURE.md) for the complete framework structure and dependency graph.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install @ulam/halohalo
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Peer dependencies: `fuse.js >= 7` (for search). Framework adapters add `react >= 18`, `vue >= 3`, or `@angular/core >= 17` as needed.
|
|
47
|
+
|
|
48
|
+
## Supported providers
|
|
49
|
+
|
|
50
|
+
- Anthropic (Claude)
|
|
51
|
+
- OpenAI (GPT)
|
|
52
|
+
- Google (Gemini)
|
|
53
|
+
|
|
54
|
+
Bring your own API key. Keys stay in the browser via localStorage and are sent directly to the provider, never to a server.
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
### Initialize
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
import { initApiKeys, initModels, getAiProvider } from '@ulam/halohalo'
|
|
62
|
+
|
|
63
|
+
initApiKeys({ anthropic: 'sk-ant-...', openai: 'sk-...' })
|
|
64
|
+
initModels({ anthropic: 'claude-sonnet-4-6', openai: 'gpt-4o' })
|
|
65
|
+
|
|
66
|
+
const provider = getAiProvider() // 'anthropic' | 'openai' | 'google'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Call a provider
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
import { createCompletion } from '@ulam/halohalo'
|
|
73
|
+
|
|
74
|
+
const result = await createCompletion({
|
|
75
|
+
prompt: 'Rewrite this finding for a mobile context.',
|
|
76
|
+
systemPrompt: 'You are an accessibility audit assistant.',
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Anthropic with tool use
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
import { callAnthropicWithTools } from '@ulam/halohalo'
|
|
84
|
+
|
|
85
|
+
const result = await callAnthropicWithTools({
|
|
86
|
+
messages,
|
|
87
|
+
tools,
|
|
88
|
+
system: 'You are an accessibility audit assistant.',
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Agentic mode
|
|
93
|
+
|
|
94
|
+
Agentic mode uses tool calling to search the corpus for similar past revisions before rewriting, matching the tone and depth of established work.
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
import { getAgenticRefinement } from '@ulam/halohalo'
|
|
98
|
+
|
|
99
|
+
const revised = await getAgenticRefinement({
|
|
100
|
+
finding,
|
|
101
|
+
notes: 'This is specific to mobile, element is a tooltip',
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### React hooks adapter
|
|
106
|
+
|
|
107
|
+
```jsx
|
|
108
|
+
import { useProviderConfig, useCompletion } from '@ulam/halohalo/react'
|
|
109
|
+
|
|
110
|
+
function AISettings() {
|
|
111
|
+
const { provider, model, setProvider } = useProviderConfig()
|
|
112
|
+
const { complete, loading } = useCompletion()
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<button onClick={() => complete('Rewrite this for mobile.')}>
|
|
116
|
+
{loading ? 'Revising...' : 'Rewrite'}
|
|
117
|
+
</button>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Vue composables adapter
|
|
123
|
+
|
|
124
|
+
`@ulam/halohalo/vue` provides composables that wrap the vanilla `createCompletion` and `createProviderConfig` with Vue reactivity. Loading and animating state are `readonly` refs that update as the completion runs.
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
import { useCompletion, useProviderConfig } from '@ulam/halohalo/vue'
|
|
128
|
+
import { onUnmounted } from 'vue'
|
|
129
|
+
|
|
130
|
+
// In setup()
|
|
131
|
+
const { loading, animating, complete, cancel, cleanup } = useCompletion()
|
|
132
|
+
onUnmounted(cleanup)
|
|
133
|
+
|
|
134
|
+
// loading.value and animating.value are reactive
|
|
135
|
+
await complete({ prompt: 'Rewrite this for mobile.' })
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`useProviderConfig()` returns reactive refs for `provider`, `models`, and `mode`, plus all setter functions from the vanilla config store.
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
const { provider, setProvider } = useProviderConfig()
|
|
142
|
+
// provider.value is reactive
|
|
143
|
+
setProvider('openai')
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Both composables return a `cleanup` function. Call it in `onUnmounted()` if the composable is used inside a component. For app-level use outside a component, cleanup is optional.
|
|
147
|
+
|
|
148
|
+
### Angular services adapter
|
|
149
|
+
|
|
150
|
+
`@ulam/halohalo/angular` provides two injectable services backed by Angular signals.
|
|
151
|
+
|
|
152
|
+
**`CompletionService`** is not `providedIn: 'root'`. Each injection creates a separate completion instance with its own loading state. Provide it at the component level for scoped instances, or at the application level for a shared one.
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import { CompletionService, ProviderConfigService, provideHalohalo } from '@ulam/halohalo/angular'
|
|
156
|
+
|
|
157
|
+
// Application root (shared singleton):
|
|
158
|
+
bootstrapApplication(AppComponent, {
|
|
159
|
+
providers: [provideHalohalo()]
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// Or component-level (scoped per component):
|
|
163
|
+
@Component({
|
|
164
|
+
providers: [CompletionService],
|
|
165
|
+
template: `
|
|
166
|
+
<button (click)="rewrite()" [disabled]="completion.loading()">
|
|
167
|
+
{{ completion.loading() ? 'Revising...' : 'Rewrite' }}
|
|
168
|
+
</button>
|
|
169
|
+
`
|
|
170
|
+
})
|
|
171
|
+
export class RewriteButtonComponent {
|
|
172
|
+
completion = inject(CompletionService)
|
|
173
|
+
|
|
174
|
+
async rewrite() {
|
|
175
|
+
await this.completion.complete({ prompt: 'Rewrite for mobile.' })
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`completion.loading()` and `completion.animating()` are Angular `computed` signals that work directly in templates and in `effect()` calls.
|
|
181
|
+
|
|
182
|
+
**`ProviderConfigService`** is `providedIn: 'root'`, giving you one config store for the whole app.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
@Component({ ... })
|
|
186
|
+
export class SettingsComponent {
|
|
187
|
+
providerConfig = inject(ProviderConfigService)
|
|
188
|
+
|
|
189
|
+
// In template:
|
|
190
|
+
// {{ providerConfig.provider() }}
|
|
191
|
+
// (click)="providerConfig.setProvider('openai')"
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Design
|
|
196
|
+
|
|
197
|
+
**Bring your own key.** No proxy, no server, no account. API keys are stored in localStorage and sent directly to the provider.
|
|
198
|
+
|
|
199
|
+
**Provider-agnostic core.** `createCompletion` works the same regardless of which provider is active. Switch providers without changing call sites.
|
|
200
|
+
|
|
201
|
+
**Vanilla-first.** The core has no framework dependency. Import from `/react`, `/vue`, or `/angular` only when you need framework reactivity.
|
|
202
|
+
|
|
203
|
+
## Subpath exports
|
|
204
|
+
|
|
205
|
+
| Import | Contents |
|
|
206
|
+
| ------ | -------- |
|
|
207
|
+
| `@ulam/halohalo` | Vanilla core: `createCompletion`, `createProviderConfig`, `callProvider`, `callAnthropicWithTools`, `getAgenticRefinement`, `searchItems`, `makeSearchTool`, and more |
|
|
208
|
+
| `@ulam/halohalo/react` | `useCompletion`, `useProviderConfig` |
|
|
209
|
+
| `@ulam/halohalo/vue` | `useCompletion`, `useProviderConfig` |
|
|
210
|
+
| `@ulam/halohalo/angular` | `CompletionService`, `ProviderConfigService`, `provideHalohalo` |
|
|
211
|
+
|
|
212
|
+
See the [root README](../../README.md) for a complete framework support overview across all ulam packages.
|
package/agenticAiService.js
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
import { callAnthropicWithTools } from './fetch.js'
|
|
2
|
-
import { makeSearchTool } from './search.js'
|
|
3
|
-
import { AI_AGENTIC_MAX_TOKENS, AGENTIC_MAX_TOOL_TURNS, LS_APIKEY_PREFIX } from './constants.js'
|
|
4
|
-
import { getAiModel } from './prefs.js'
|
|
5
|
-
import { parseAiResponse } from './aiService.js'
|
|
6
|
-
import { getSystemPrompt } from './init.js'
|
|
7
|
-
|
|
8
|
-
export { AiApiError } from './providers.js'
|
|
9
|
-
|
|
10
|
-
const FALLBACK_SYSTEM_PROMPT = `You are an AI assistant helping rewrite text entries based on user notes. Search for related entries before rewriting, then produce a revised description and suggested fix.
|
|
11
|
-
|
|
12
|
-
Format your final output as exactly two lines:
|
|
13
|
-
Description: [rewritten description]
|
|
14
|
-
Suggested Fix: [rewritten suggested fix]`
|
|
15
|
-
|
|
16
|
-
const CORPUS_SEARCH_FIELDS = [
|
|
17
|
-
{ name: 'title', weight: 0.32 },
|
|
18
|
-
{ name: 'keywords', weight: 0.30 },
|
|
19
|
-
{ name: 'desc', weight: 0.07 },
|
|
20
|
-
{ name: 'fix', weight: 0.03 },
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
const CORPUS_PICK = ['id', 'title', 'primarySC', 'severity', 'desc', 'fix']
|
|
24
|
-
|
|
25
|
-
export async function getAgenticRefinement({ finding, descText, fixText, note, corpus }) {
|
|
26
|
-
const { getAdapter } = await import('@ulam/sawsawan')
|
|
27
|
-
const key = await getAdapter().getKey(`${LS_APIKEY_PREFIX}anthropic`)
|
|
28
|
-
|
|
29
|
-
if (!key) throw new Error('Anthropic API key required for agentic mode. Add one in Settings → AI Assist.')
|
|
30
|
-
|
|
31
|
-
const model = getAiModel('anthropic')
|
|
32
|
-
|
|
33
|
-
const { schema: toolSchema, handler: toolHandler } = makeSearchTool(corpus, {
|
|
34
|
-
name: 'search_corpus',
|
|
35
|
-
description:
|
|
36
|
-
'Search the accessibility finding corpus for entries related to a natural-language query. ' +
|
|
37
|
-
'Call this before rewriting to find similar findings that demonstrate the expected voice, ' +
|
|
38
|
-
'tone, and technical depth. Returns up to 3 matching entries.',
|
|
39
|
-
queryDescription: 'Natural-language search query, e.g. "keyboard focus visible" or "color contrast low vision".',
|
|
40
|
-
fields: CORPUS_SEARCH_FIELDS,
|
|
41
|
-
pick: CORPUS_PICK,
|
|
42
|
-
limit: 3,
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
const userPrompt = `Refine this accessibility finding based on the auditor's note.
|
|
46
|
-
|
|
47
|
-
Title: ${finding.title}
|
|
48
|
-
WCAG SC: ${finding.primarySC}
|
|
49
|
-
Severity: ${finding.severity}
|
|
50
|
-
Platform: ${finding.platform}
|
|
51
|
-
|
|
52
|
-
Current description:
|
|
53
|
-
${descText}
|
|
54
|
-
|
|
55
|
-
Current suggested fix:
|
|
56
|
-
${fixText}
|
|
57
|
-
|
|
58
|
-
Auditor's note: "${note}"
|
|
59
|
-
|
|
60
|
-
Search the corpus for related findings, then rewrite the description and suggested fix to reflect the refinement.`
|
|
61
|
-
|
|
62
|
-
const messages = [{ role: 'user', content: userPrompt }]
|
|
63
|
-
|
|
64
|
-
const text = await callAnthropicWithTools({
|
|
65
|
-
key,
|
|
66
|
-
model,
|
|
67
|
-
system: getSystemPrompt() || FALLBACK_SYSTEM_PROMPT,
|
|
68
|
-
tools: [toolSchema],
|
|
69
|
-
messages,
|
|
70
|
-
maxTokens: AI_AGENTIC_MAX_TOKENS,
|
|
71
|
-
maxTurns: AGENTIC_MAX_TOOL_TURNS,
|
|
72
|
-
onToolCall: toolHandler,
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
return parseAiResponse(text)
|
|
76
|
-
}
|
|
1
|
+
import { callAnthropicWithTools } from './fetch.js'
|
|
2
|
+
import { makeSearchTool } from './search.js'
|
|
3
|
+
import { AI_AGENTIC_MAX_TOKENS, AGENTIC_MAX_TOOL_TURNS, LS_APIKEY_PREFIX } from './constants.js'
|
|
4
|
+
import { getAiModel } from './prefs.js'
|
|
5
|
+
import { parseAiResponse } from './aiService.js'
|
|
6
|
+
import { getSystemPrompt } from './init.js'
|
|
7
|
+
|
|
8
|
+
export { AiApiError } from './providers.js'
|
|
9
|
+
|
|
10
|
+
const FALLBACK_SYSTEM_PROMPT = `You are an AI assistant helping rewrite text entries based on user notes. Search for related entries before rewriting, then produce a revised description and suggested fix.
|
|
11
|
+
|
|
12
|
+
Format your final output as exactly two lines:
|
|
13
|
+
Description: [rewritten description]
|
|
14
|
+
Suggested Fix: [rewritten suggested fix]`
|
|
15
|
+
|
|
16
|
+
const CORPUS_SEARCH_FIELDS = [
|
|
17
|
+
{ name: 'title', weight: 0.32 },
|
|
18
|
+
{ name: 'keywords', weight: 0.30 },
|
|
19
|
+
{ name: 'desc', weight: 0.07 },
|
|
20
|
+
{ name: 'fix', weight: 0.03 },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const CORPUS_PICK = ['id', 'title', 'primarySC', 'severity', 'desc', 'fix']
|
|
24
|
+
|
|
25
|
+
export async function getAgenticRefinement({ finding, descText, fixText, note, corpus }) {
|
|
26
|
+
const { getAdapter } = await import('@ulam/sawsawan')
|
|
27
|
+
const key = await getAdapter().getKey(`${LS_APIKEY_PREFIX}anthropic`)
|
|
28
|
+
|
|
29
|
+
if (!key) throw new Error('Anthropic API key required for agentic mode. Add one in Settings → AI Assist.')
|
|
30
|
+
|
|
31
|
+
const model = getAiModel('anthropic')
|
|
32
|
+
|
|
33
|
+
const { schema: toolSchema, handler: toolHandler } = makeSearchTool(corpus, {
|
|
34
|
+
name: 'search_corpus',
|
|
35
|
+
description:
|
|
36
|
+
'Search the accessibility finding corpus for entries related to a natural-language query. ' +
|
|
37
|
+
'Call this before rewriting to find similar findings that demonstrate the expected voice, ' +
|
|
38
|
+
'tone, and technical depth. Returns up to 3 matching entries.',
|
|
39
|
+
queryDescription: 'Natural-language search query, e.g. "keyboard focus visible" or "color contrast low vision".',
|
|
40
|
+
fields: CORPUS_SEARCH_FIELDS,
|
|
41
|
+
pick: CORPUS_PICK,
|
|
42
|
+
limit: 3,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const userPrompt = `Refine this accessibility finding based on the auditor's note.
|
|
46
|
+
|
|
47
|
+
Title: ${finding.title}
|
|
48
|
+
WCAG SC: ${finding.primarySC}
|
|
49
|
+
Severity: ${finding.severity}
|
|
50
|
+
Platform: ${finding.platform}
|
|
51
|
+
|
|
52
|
+
Current description:
|
|
53
|
+
${descText}
|
|
54
|
+
|
|
55
|
+
Current suggested fix:
|
|
56
|
+
${fixText}
|
|
57
|
+
|
|
58
|
+
Auditor's note: "${note}"
|
|
59
|
+
|
|
60
|
+
Search the corpus for related findings, then rewrite the description and suggested fix to reflect the refinement.`
|
|
61
|
+
|
|
62
|
+
const messages = [{ role: 'user', content: userPrompt }]
|
|
63
|
+
|
|
64
|
+
const text = await callAnthropicWithTools({
|
|
65
|
+
key,
|
|
66
|
+
model,
|
|
67
|
+
system: getSystemPrompt() || FALLBACK_SYSTEM_PROMPT,
|
|
68
|
+
tools: [toolSchema],
|
|
69
|
+
messages,
|
|
70
|
+
maxTokens: AI_AGENTIC_MAX_TOKENS,
|
|
71
|
+
maxTurns: AGENTIC_MAX_TOOL_TURNS,
|
|
72
|
+
onToolCall: toolHandler,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
return parseAiResponse(text)
|
|
76
|
+
}
|
package/aiService.js
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import { callProvider } from './fetch.js'
|
|
2
|
-
import { AI_MAX_TOKENS, AI_DESC_REGEX, AI_FIX_REGEX, LS_APIKEY_PREFIX } from './constants.js'
|
|
3
|
-
import { getAiProvider, getAiModel } from './prefs.js'
|
|
4
|
-
import { getBuildPrompt } from './init.js'
|
|
5
|
-
|
|
6
|
-
export { AiApiError, httpStatusToErrorType } from './providers.js'
|
|
7
|
-
|
|
8
|
-
export function parseAiResponse(text) {
|
|
9
|
-
const descMatch = text.match(AI_DESC_REGEX)
|
|
10
|
-
const fixMatch = text.match(AI_FIX_REGEX)
|
|
11
|
-
return {
|
|
12
|
-
desc: descMatch?.[1]?.trim() || null,
|
|
13
|
-
fix: fixMatch?.[1]?.trim() || null,
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function getAiRefinement({ finding, descText, fixText, note }) {
|
|
18
|
-
const buildPrompt = getBuildPrompt()
|
|
19
|
-
if (!buildPrompt) throw new Error('halohalo: call initHalohalo({ buildPrompt }) before getAiRefinement')
|
|
20
|
-
|
|
21
|
-
const provider = getAiProvider()
|
|
22
|
-
const { getAdapter } = await import('@ulam/sawsawan')
|
|
23
|
-
const key = await getAdapter().getKey(`${LS_APIKEY_PREFIX}${provider}`)
|
|
24
|
-
|
|
25
|
-
if (!key) throw new Error(`No API key found for ${provider}. Add one in Settings.`)
|
|
26
|
-
|
|
27
|
-
const model = getAiModel(provider)
|
|
28
|
-
const prompt = buildPrompt({ finding, descText, fixText, note })
|
|
29
|
-
const text = await callProvider({ provider, model, key, prompt, maxTokens: AI_MAX_TOKENS })
|
|
30
|
-
return parseAiResponse(text)
|
|
31
|
-
}
|
|
1
|
+
import { callProvider } from './fetch.js'
|
|
2
|
+
import { AI_MAX_TOKENS, AI_DESC_REGEX, AI_FIX_REGEX, LS_APIKEY_PREFIX } from './constants.js'
|
|
3
|
+
import { getAiProvider, getAiModel } from './prefs.js'
|
|
4
|
+
import { getBuildPrompt } from './init.js'
|
|
5
|
+
|
|
6
|
+
export { AiApiError, httpStatusToErrorType } from './providers.js'
|
|
7
|
+
|
|
8
|
+
export function parseAiResponse(text) {
|
|
9
|
+
const descMatch = text.match(AI_DESC_REGEX)
|
|
10
|
+
const fixMatch = text.match(AI_FIX_REGEX)
|
|
11
|
+
return {
|
|
12
|
+
desc: descMatch?.[1]?.trim() || null,
|
|
13
|
+
fix: fixMatch?.[1]?.trim() || null,
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function getAiRefinement({ finding, descText, fixText, note }) {
|
|
18
|
+
const buildPrompt = getBuildPrompt()
|
|
19
|
+
if (!buildPrompt) throw new Error('halohalo: call initHalohalo({ buildPrompt }) before getAiRefinement')
|
|
20
|
+
|
|
21
|
+
const provider = getAiProvider()
|
|
22
|
+
const { getAdapter } = await import('@ulam/sawsawan')
|
|
23
|
+
const key = await getAdapter().getKey(`${LS_APIKEY_PREFIX}${provider}`)
|
|
24
|
+
|
|
25
|
+
if (!key) throw new Error(`No API key found for ${provider}. Add one in Settings.`)
|
|
26
|
+
|
|
27
|
+
const model = getAiModel(provider)
|
|
28
|
+
const prompt = buildPrompt({ finding, descText, fixText, note })
|
|
29
|
+
const text = await callProvider({ provider, model, key, prompt, maxTokens: AI_MAX_TOKENS })
|
|
30
|
+
return parseAiResponse(text)
|
|
31
|
+
}
|