howone 0.2.2 → 0.2.5
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/package.json +1 -1
- package/templates/vite/.howone/skills/howone/01-architect/01-app-generation.md +36 -7
- package/templates/vite/.howone/skills/howone/01-architect/02-manifest-codegen.md +121 -436
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/03-service-capability-catalog.md +37 -19
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/04-workflow-operations.md +14 -5
- package/templates/vite/.howone/skills/howone/04-app-sdk/01-client-setup.md +94 -261
- package/templates/vite/.howone/skills/howone/04-app-sdk/02-entity-operations.md +85 -465
- package/templates/vite/.howone/skills/howone/04-app-sdk/03-auth.md +11 -7
- package/templates/vite/.howone/skills/howone/04-app-sdk/04-react-integration.md +84 -137
- package/templates/vite/.howone/skills/howone/04-app-sdk/05-file-upload.md +66 -273
- package/templates/vite/.howone/skills/howone/04-app-sdk/06-raw-http.md +72 -249
- package/templates/vite/.howone/skills/howone/04-app-sdk/07-ai-action-calls.md +135 -499
- package/templates/vite/.howone/skills/howone/04-app-sdk/08-ai-manifest-handoff.md +49 -196
- package/templates/vite/.howone/skills/howone/04-app-sdk/09-extension-boundaries.md +4 -4
- package/templates/vite/.howone/skills/howone/04-app-sdk/10-workflow-execute-sse.md +94 -61
- package/templates/vite/.howone/skills/howone/04-app-sdk/11-entity-data-access-patterns.md +4 -3
- package/templates/vite/.howone/skills/howone/SKILL.md +110 -4
- package/templates/vite/.howone/skills/howone/references/audio-generation.md +30 -0
- package/templates/vite/.howone/skills/howone/references/audio-recognition.md +30 -0
- package/templates/vite/.howone/skills/howone/references/common-errors.md +27 -0
- package/templates/vite/.howone/skills/howone/references/finance.md +28 -0
- package/templates/vite/.howone/skills/howone/references/image-editing.md +30 -0
- package/templates/vite/.howone/skills/howone/references/image-generation.md +30 -0
- package/templates/vite/.howone/skills/howone/references/version-evidence.md +47 -0
- package/templates/vite/.howone/skills/howone/references/video-generation.md +35 -0
- package/templates/vite/.howone/skills/howone/scripts/verify-project.mjs +151 -0
- package/templates/vite/package.json +1 -1
- package/templates/vite/src/App.tsx +9 -5
- package/templates/vite/src/lib/sdk.ts +7 -5
- package/templates/vite/bun.lock +0 -1478
|
@@ -1,41 +1,27 @@
|
|
|
1
|
-
# AI Manifest Handoff
|
|
1
|
+
# AI Manifest Handoff
|
|
2
2
|
|
|
3
|
-
Use this
|
|
4
|
-
|
|
3
|
+
Use this track after `sync_ai_artifacts` and external workflow submission. It is the short handoff
|
|
4
|
+
from `.howone/ai/manifest.json` to `src/lib/sdk.ts`; use `07-ai-action-calls.md` for execution
|
|
5
|
+
semantics and `10-workflow-execute-sse.md` for wire details.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
call it?**
|
|
7
|
+
## Handoff protocol
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
1. Read the current manifest, not a cached tool response.
|
|
10
|
+
2. Confirm every action has an exact UUID `workflowId`.
|
|
11
|
+
3. Generate Zod input/output schemas from the manifest.
|
|
12
|
+
4. Bind with `defineAiAction` and compose with `withAiActions`.
|
|
13
|
+
5. Run `node .howone/skills/howone/scripts/verify-project.mjs`, typecheck, and build.
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
Workflow create/update can rotate the config UUID. After every successful external workflow
|
|
16
|
+
operation, sync and re-read the manifest before deciding whether the app binding changed.
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
There is no action-name fallback. A missing UUID is a module initialization error, not a server
|
|
19
|
+
request that can be repaired later.
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
1. Read stable action name/ID.
|
|
20
|
-
2. Read `workflowId`.
|
|
21
|
-
3. Read `inputSchema`.
|
|
22
|
-
4. Read `outputSchema`.
|
|
23
|
-
5. Generate Zod input and output schemas.
|
|
24
|
-
6. Bind with `defineAiAction(name, { workflowId, inputSchema, outputSchema })`.
|
|
25
|
-
7. Compose with `withAiActions(client, ai)`.
|
|
26
|
-
|
|
27
|
-
`workflowId` is mandatory. Without it, the SDK falls back to the action name as the execution URL
|
|
28
|
-
segment, and the workflow service will reject it because the segment is not a UUID.
|
|
29
|
-
|
|
30
|
-
## Generated Binding Example
|
|
21
|
+
## Example
|
|
31
22
|
|
|
32
23
|
```ts
|
|
33
|
-
import {
|
|
34
|
-
createClient,
|
|
35
|
-
defineAiAction,
|
|
36
|
-
defineAiActions,
|
|
37
|
-
withAiActions,
|
|
38
|
-
} from '@howone/sdk'
|
|
24
|
+
import { createClient, defineAiAction, defineAiActions, withAiActions } from '@howone/sdk'
|
|
39
25
|
import { z } from 'zod'
|
|
40
26
|
|
|
41
27
|
const client = createClient({
|
|
@@ -43,22 +29,14 @@ const client = createClient({
|
|
|
43
29
|
env: import.meta.env.VITE_HOWONE_ENV,
|
|
44
30
|
})
|
|
45
31
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
summary_length: z.string().optional(),
|
|
49
|
-
})
|
|
50
|
-
export type SummarizeDocumentInput = z.infer<typeof summarizeDocumentInputSchema>
|
|
32
|
+
const inputSchema = z.object({ documentUrl: z.string().url() })
|
|
33
|
+
const outputSchema = z.object({ summary: z.string() })
|
|
51
34
|
|
|
52
|
-
|
|
53
|
-
summary: z.string(),
|
|
54
|
-
})
|
|
55
|
-
export type SummarizeDocumentOutput = z.infer<typeof summarizeDocumentOutputSchema>
|
|
56
|
-
|
|
57
|
-
export const ai = defineAiActions({
|
|
35
|
+
const ai = defineAiActions({
|
|
58
36
|
summarizeDocument: defineAiAction('summarizeDocument', {
|
|
59
37
|
workflowId: '550e8400-e29b-41d4-a716-446655440000',
|
|
60
|
-
inputSchema
|
|
61
|
-
outputSchema
|
|
38
|
+
inputSchema,
|
|
39
|
+
outputSchema,
|
|
62
40
|
}),
|
|
63
41
|
})
|
|
64
42
|
|
|
@@ -66,172 +44,47 @@ const howone = withAiActions(client, ai)
|
|
|
66
44
|
export default howone
|
|
67
45
|
```
|
|
68
46
|
|
|
69
|
-
|
|
47
|
+
The UUID above is illustrative only. Copy the real value from the current manifest.
|
|
48
|
+
|
|
49
|
+
## Schema conversion rules
|
|
70
50
|
|
|
71
51
|
| JSON Schema | Zod |
|
|
72
52
|
|---|---|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
| `number`
|
|
76
|
-
|
|
|
77
|
-
|
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
| field not in `required` | `.optional()` |
|
|
83
|
-
| nullable | `.nullable()` |
|
|
84
|
-
|
|
85
|
-
Rules:
|
|
86
|
-
|
|
87
|
-
- Required manifest fields must stay required in Zod.
|
|
88
|
-
- Do not add `.passthrough()` to hide execution envelope problems.
|
|
89
|
-
- Do not make outputs optional to silence validation failures.
|
|
90
|
-
- If the workflow returns a different shape, fix the workflow/capability contract.
|
|
91
|
-
|
|
92
|
-
## Calling Actions
|
|
93
|
-
|
|
94
|
-
For typed one-shot actions:
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
const output = await howone.ai.summarizeDocument.run({
|
|
98
|
-
document_url,
|
|
99
|
-
summary_length: 'short',
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
setSummary(output.summary)
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
When `outputSchema` exists, `.run()` returns the validated `finalResult` payload directly.
|
|
106
|
-
|
|
107
|
-
Do not read:
|
|
108
|
-
|
|
109
|
-
```ts
|
|
110
|
-
result.finalResult.summary
|
|
111
|
-
result.data.summary
|
|
112
|
-
result.raw.finalResult
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
Those are execution-envelope paths, not the typed SDK action contract.
|
|
116
|
-
|
|
117
|
-
## Streaming And Events
|
|
118
|
-
|
|
119
|
-
Use `.stream()` when UI needs live output or cancellation:
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
const session = howone.ai.generateStory.stream(input, {
|
|
123
|
-
onMessageChunk: (line) => appendLog(line),
|
|
124
|
-
onProgress: (percent, line) => {
|
|
125
|
-
if (line?.startsWith('[DISPLAY]')) setStatus(line.replace('[DISPLAY]', '').trim())
|
|
126
|
-
if (percent === 100) setProgress(100)
|
|
127
|
-
},
|
|
128
|
-
onCreditInsufficient: (event) => showCreditError(event.message),
|
|
129
|
-
onRunError: (event) => showExecutionError(event.message),
|
|
130
|
-
onError: (error) => setError(error.message),
|
|
131
|
-
onComplete: (result) => setRawResult(result),
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
cancelButton.onclick = () => session.cancel()
|
|
135
|
-
const final = await session.result
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
Use `.events()` when code wants an async iterable:
|
|
139
|
-
|
|
140
|
-
```ts
|
|
141
|
-
for await (const event of howone.ai.generateStory.events(input)) {
|
|
142
|
-
if (event.type === 'progress') {
|
|
143
|
-
appendLog(String(event.message))
|
|
144
|
-
}
|
|
145
|
-
if (event.type === 'run_complete') {
|
|
146
|
-
setFinalResult(event.message)
|
|
147
|
-
}
|
|
148
|
-
if (event.type === 'credit_insufficient') {
|
|
149
|
-
showCreditError(String(event.message))
|
|
150
|
-
}
|
|
151
|
-
if (event.type === 'run_error') {
|
|
152
|
-
showExecutionError(String(event.message))
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
## UI State
|
|
158
|
-
|
|
159
|
-
The SDK returns data and exposes callbacks. The app owns all visible UI.
|
|
160
|
-
|
|
161
|
-
Recommended states:
|
|
162
|
-
|
|
163
|
-
```ts
|
|
164
|
-
type AiUiState<T> =
|
|
165
|
-
| { status: 'idle' }
|
|
166
|
-
| { status: 'running'; progress?: number }
|
|
167
|
-
| { status: 'succeeded'; output: T }
|
|
168
|
-
| { status: 'failed'; message: string }
|
|
169
|
-
| { status: 'cancelled' }
|
|
170
|
-
```
|
|
53
|
+
| string | `z.string()` |
|
|
54
|
+
| string URI | `z.string().url()` |
|
|
55
|
+
| number/integer | `z.number()` / `z.number().int()` |
|
|
56
|
+
| boolean | `z.boolean()` |
|
|
57
|
+
| array | `z.array(item)` |
|
|
58
|
+
| object | `z.object({ ... })` |
|
|
59
|
+
| enum | `z.enum([...])` |
|
|
60
|
+
| nullable | `z.nullable(...)` |
|
|
61
|
+
| not required | `.optional()` |
|
|
171
62
|
|
|
172
|
-
Do not
|
|
63
|
+
Do not make required fields optional and do not use `.passthrough()` to conceal a workflow result
|
|
64
|
+
shape mismatch. Typed `.run()` returns the validated final result directly.
|
|
173
65
|
|
|
174
|
-
##
|
|
175
|
-
|
|
176
|
-
If AI output should survive refresh, use entity persistence after the action returns.
|
|
177
|
-
|
|
178
|
-
For history-style products, prefer `runAiActionAndPersist()`:
|
|
66
|
+
## Result handoff
|
|
179
67
|
|
|
180
68
|
```ts
|
|
181
|
-
const
|
|
182
|
-
entity: howone.entities.Generation,
|
|
183
|
-
input: { prompt },
|
|
184
|
-
createPending: (input) => ({
|
|
185
|
-
prompt: input.prompt,
|
|
186
|
-
status: 'pending',
|
|
187
|
-
requestedAt: new Date().toISOString(),
|
|
188
|
-
}),
|
|
189
|
-
run: (input) => howone.ai.generateImage.run(input),
|
|
190
|
-
mapCompleted: ({ output }) => ({
|
|
191
|
-
status: 'completed',
|
|
192
|
-
resultUrl: output.generated_image_url,
|
|
193
|
-
completedAt: new Date().toISOString(),
|
|
194
|
-
}),
|
|
195
|
-
mapFailed: ({ error }) => ({
|
|
196
|
-
status: 'failed',
|
|
197
|
-
errorMessage: error instanceof Error ? error.message : 'Generation failed',
|
|
198
|
-
}),
|
|
199
|
-
})
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
For simple save-after-success:
|
|
203
|
-
|
|
204
|
-
```ts
|
|
205
|
-
const output = await howone.ai.summarizeDocument.run(input)
|
|
69
|
+
const output = await howone.ai.summarizeDocument.run({ documentUrl })
|
|
206
70
|
await howone.entities.DocumentSummary.create({
|
|
207
|
-
documentUrl
|
|
71
|
+
documentUrl,
|
|
208
72
|
summary: output.summary,
|
|
209
73
|
status: 'completed',
|
|
210
74
|
})
|
|
211
75
|
```
|
|
212
76
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
## Workflow Edit Handoff
|
|
216
|
-
|
|
217
|
-
When changing external workflow behavior later:
|
|
218
|
-
|
|
219
|
-
1. If schema changes, update AI capability contract first and sync manifest.
|
|
220
|
-
2. Submit update through `external-ai-capability` with `updates: [{ capabilityName, updatePrompt }]`.
|
|
221
|
-
3. Re-read `.howone/ai/manifest.json`; update may rotate `workflowId` to a fresh config UUID.
|
|
222
|
-
4. Regenerate SDK bindings whenever the manifest `workflowId`, input schema, or output schema changed.
|
|
223
|
-
5. Behavior-only updates still require checking the manifest before deciding SDK is unchanged.
|
|
77
|
+
For history, use pending-first `runAiActionAndPersist`. The workflow does not perform database CRUD;
|
|
78
|
+
the app persists through generated entity bindings.
|
|
224
79
|
|
|
225
|
-
|
|
226
|
-
|
|
80
|
+
For streaming, use `onMessageChunk`, await `session.result`, and catch terminal workflow errors.
|
|
81
|
+
For events, a terminal error event is yielded before the iterator rejects.
|
|
227
82
|
|
|
228
|
-
##
|
|
83
|
+
## Final checklist
|
|
229
84
|
|
|
230
|
-
-
|
|
231
|
-
-
|
|
232
|
-
- Zod
|
|
233
|
-
- `
|
|
234
|
-
-
|
|
235
|
-
-
|
|
236
|
-
- Persistence goes through `howone.entities.*`.
|
|
237
|
-
- Visible status/error UI is app-owned.
|
|
85
|
+
- [ ] Current AI manifest was read after the last sync.
|
|
86
|
+
- [ ] Action IDs and UUIDs match exactly.
|
|
87
|
+
- [ ] Zod requiredness/enums match the manifest.
|
|
88
|
+
- [ ] No `aiUrl`, `aiBaseUrl`, or raw EAX URL was added.
|
|
89
|
+
- [ ] App uses `HowOneProvider client={howone}`.
|
|
90
|
+
- [ ] Verifier, typecheck, and build pass.
|
|
@@ -86,7 +86,7 @@ Use callbacks instead:
|
|
|
86
86
|
|
|
87
87
|
```tsx
|
|
88
88
|
<HowOneProvider
|
|
89
|
-
|
|
89
|
+
client={howone}
|
|
90
90
|
brand="visible"
|
|
91
91
|
onAuthRedirect={({ mode, returnUrl }) => {
|
|
92
92
|
setAuthUi({ redirecting: true, mode, returnUrl })
|
|
@@ -102,8 +102,8 @@ Use callbacks instead:
|
|
|
102
102
|
Hide the logo only when explicitly requested:
|
|
103
103
|
|
|
104
104
|
```tsx
|
|
105
|
-
<HowOneProvider brand="hidden" />
|
|
106
|
-
<HowOneProvider showBrandButton={false} />
|
|
105
|
+
<HowOneProvider client={howone} brand="hidden" />
|
|
106
|
+
<HowOneProvider client={howone} showBrandButton={false} />
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
## UI Feedback Rule
|
|
@@ -132,7 +132,7 @@ For streaming workflows, use callbacks/events:
|
|
|
132
132
|
const session = howone.ai.generateImage.stream(
|
|
133
133
|
{ prompt },
|
|
134
134
|
{
|
|
135
|
-
|
|
135
|
+
onMessageChunk: (delta) => appendLog(delta),
|
|
136
136
|
onProgress: (progress) => setProgress(progress),
|
|
137
137
|
onError: (error) => setStatus({ type: 'error', message: error.message }),
|
|
138
138
|
onComplete: (result) => setResult(result.finalResult),
|
|
@@ -1,52 +1,40 @@
|
|
|
1
1
|
# Workflow Execute SSE
|
|
2
2
|
|
|
3
|
-
Use this reference
|
|
3
|
+
Use this reference when an app needs live workflow progress, cancellation, or raw event iteration.
|
|
4
|
+
For ordinary AI actions, start with `07-ai-action-calls.md` and use the typed action methods.
|
|
4
5
|
|
|
5
|
-
##
|
|
6
|
+
## Environment and endpoint
|
|
6
7
|
|
|
7
|
-
The SDK
|
|
8
|
+
The SDK chooses the EAX base from `createClient({ env })`:
|
|
8
9
|
|
|
9
|
-
|
|
|
10
|
+
| `env` | EAX base |
|
|
10
11
|
|---|---|
|
|
11
|
-
| `
|
|
12
|
+
| `local` | `https://eax-backend-orchestrator-dev.fly.dev` |
|
|
13
|
+
| `dev` | `https://eax-backend-orchestrator-dev.fly.dev` |
|
|
14
|
+
| `prod` | `https://eax-backend-orchestrator-prod.fly.dev` |
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
Do not construct this URL in app code or override it with `aiUrl`. A workflow action calls:
|
|
14
17
|
|
|
15
|
-
```
|
|
16
|
-
{
|
|
17
|
-
"inputs": {},
|
|
18
|
-
"priority": "normal"
|
|
19
|
-
}
|
|
18
|
+
```text
|
|
19
|
+
POST {eaxBase}/workflow/execute/{projectId}/{workflowId}
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
`priority
|
|
23
|
-
|
|
24
|
-
The successful HTTP response includes:
|
|
25
|
-
|
|
26
|
-
| Header | Meaning |
|
|
27
|
-
|---|---|
|
|
28
|
-
| `Content-Type: text/event-stream` | Streaming response |
|
|
29
|
-
| `X-Run-Id` | Execution task id for status/cancel follow-up |
|
|
30
|
-
|
|
31
|
-
Do not use or generate code for old endpoints such as
|
|
32
|
-
`/workflow/{appId}/{workflowId}/execute_sse`.
|
|
22
|
+
The request body is `{ inputs: {}, priority?: 'low' | 'normal' | 'high' }` and carries the current
|
|
23
|
+
client token as `Authorization: Bearer ...`.
|
|
33
24
|
|
|
34
|
-
## Wire
|
|
25
|
+
## Wire protocol
|
|
35
26
|
|
|
36
|
-
Frames
|
|
27
|
+
Frames are JSON `data:` lines separated by a blank line. The JSON envelope owns the ID and event
|
|
28
|
+
name; there are no separate legacy SSE `id:` or `event:` lines:
|
|
37
29
|
|
|
38
30
|
```text
|
|
39
|
-
data: {"id":"
|
|
31
|
+
data: {"id":"evt_1","event":"run_start","message":"execution with sse is started"}
|
|
40
32
|
|
|
41
|
-
data: {"id":"
|
|
33
|
+
data: {"id":"evt_2","event":"progress","message":"[DISPLAY] Executing node: Extract"}
|
|
42
34
|
|
|
43
|
-
data: {"id":"
|
|
35
|
+
data: {"id":"evt_3","event":"run_complete","message":{"imageUrl":"https://..."}}
|
|
44
36
|
```
|
|
45
37
|
|
|
46
|
-
There are no separate SSE `event:` or `id:` lines. The JSON envelope owns those fields.
|
|
47
|
-
|
|
48
|
-
## Envelope
|
|
49
|
-
|
|
50
38
|
```ts
|
|
51
39
|
type WorkflowSseEnvelope = {
|
|
52
40
|
id: string
|
|
@@ -55,51 +43,96 @@ type WorkflowSseEnvelope = {
|
|
|
55
43
|
}
|
|
56
44
|
```
|
|
57
45
|
|
|
58
|
-
Event meanings:
|
|
59
|
-
|
|
60
|
-
| Event | Message | Meaning |
|
|
61
|
-
|---|---|---|
|
|
62
|
-
| `run_start` | string | Stream opened and execution started. |
|
|
63
|
-
| `progress` | string | One worker log line. `[DISPLAY]` lines are intended for UI. |
|
|
64
|
-
| `run_complete` | object | Final workflow-specific output. SDK maps this to `AiResult.finalResult`. |
|
|
65
|
-
| `run_error` | string | Workflow failed. |
|
|
66
|
-
| `credit_insufficient` | string | Billing/credit block. |
|
|
67
|
-
|
|
68
46
|
The terminal event is exactly one of `run_complete`, `run_error`, or `credit_insufficient`.
|
|
69
|
-
There is no `stream_end` event
|
|
70
|
-
|
|
71
|
-
## SDK Mapping
|
|
72
|
-
|
|
73
|
-
`howone.ai.run(configId, inputs)` and typed action `.run()` call the endpoint above.
|
|
47
|
+
There is no `stream_end`, `node_start`, `tool_call_end`, or `ai_message_chunk` event in this
|
|
48
|
+
workflow endpoint.
|
|
74
49
|
|
|
75
|
-
|
|
50
|
+
## SDK mapping
|
|
76
51
|
|
|
77
52
|
```ts
|
|
78
53
|
type AiResult = {
|
|
79
54
|
success: boolean
|
|
80
|
-
runId?: string
|
|
55
|
+
runId?: string // X-Run-Id response header
|
|
81
56
|
outcome: 'success' | 'credit_insufficient' | 'run_error' | null
|
|
82
57
|
finalResult: Record<string, unknown> | null
|
|
83
58
|
progressLogs: string[]
|
|
59
|
+
stateData: Record<string, unknown> // deprecated; always empty for this protocol
|
|
60
|
+
nodeExecutions: unknown[] // deprecated; always empty
|
|
61
|
+
toolExecutions: unknown[] // deprecated; always empty
|
|
62
|
+
totalDuration: number
|
|
84
63
|
errors: string[]
|
|
85
64
|
events: AiEvent[]
|
|
86
65
|
}
|
|
87
66
|
```
|
|
88
67
|
|
|
89
|
-
|
|
90
|
-
|
|
68
|
+
`run_complete.message` becomes `finalResult`. `progress.message` lines append to `progressLogs`.
|
|
69
|
+
`run_error` and `credit_insufficient` append errors and cause `run()`/`stream.result` to reject with
|
|
70
|
+
`WorkflowExecutionError`; the `events()` iterator yields the terminal event before rejecting.
|
|
71
|
+
|
|
72
|
+
## UI pattern
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
function WorkflowProgress({ input }: { input: GenerateInput }) {
|
|
76
|
+
const [logs, setLogs] = useState<string[]>([])
|
|
77
|
+
const [error, setError] = useState<string | null>(null)
|
|
78
|
+
|
|
79
|
+
async function run() {
|
|
80
|
+
const controller = new AbortController()
|
|
81
|
+
try {
|
|
82
|
+
const result = await howone.ai.generate.run(input, {
|
|
83
|
+
signal: controller.signal,
|
|
84
|
+
onMessageChunk: (line) => setLogs((current) => [...current, line]),
|
|
85
|
+
})
|
|
86
|
+
console.log(result)
|
|
87
|
+
} catch (value) {
|
|
88
|
+
setError(value instanceof Error ? value.message : String(value))
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return <button onClick={() => void run()}>Run workflow</button>
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For a cancel button, keep the controller/session in a ref and call `abort()` or `session.cancel()`.
|
|
97
|
+
Treat abort as a user action in UI; do not report it as an EAX `run_error`.
|
|
91
98
|
|
|
92
|
-
## Callback
|
|
99
|
+
## Callback rules
|
|
93
100
|
|
|
94
|
-
- `
|
|
95
|
-
- `
|
|
96
|
-
- `
|
|
97
|
-
- `
|
|
98
|
-
|
|
99
|
-
- `
|
|
100
|
-
- `
|
|
101
|
+
- `onEvent(event, 'workflow')` fires for each parsed envelope.
|
|
102
|
+
- `onRunStart` fires for `run_start`.
|
|
103
|
+
- `onMessageChunk(text, event)` fires for each non-empty progress line.
|
|
104
|
+
- `onProgress(0, message)` is a compatibility callback for progress lines; `onProgress(100)` fires
|
|
105
|
+
on successful completion.
|
|
106
|
+
- `onRunComplete` receives the final object.
|
|
107
|
+
- `onRunError` and `onCreditInsufficient` receive terminal messages.
|
|
108
|
+
- `onComplete` receives the assembled result before a terminal failure is rejected.
|
|
109
|
+
- `onError` receives transport and terminal errors.
|
|
101
110
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
111
|
+
Callbacks are not a substitute for awaiting `run()` or `session.result`; always handle the promise.
|
|
112
|
+
The SDK retains at most 1,000 events and 500 progress lines by default while still invoking callbacks
|
|
113
|
+
for every event. Configure the bounds for a diagnostic screen rather than retaining unbounded logs.
|
|
105
114
|
|
|
115
|
+
## Raw event iteration
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
try {
|
|
119
|
+
for await (const event of howone.ai.generate.events(input)) {
|
|
120
|
+
renderEvent(event)
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
renderFailure(error)
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
On a terminal failure, consumers see the `run_error` or `credit_insufficient` event before the
|
|
128
|
+
iterator rejects. Breaking the loop cancels the underlying reader.
|
|
129
|
+
|
|
130
|
+
## Common mistakes
|
|
131
|
+
|
|
132
|
+
| Mistake | Fix |
|
|
133
|
+
|---|---|
|
|
134
|
+
| Calling the old `/workflow/{appId}/{id}/execute_sse` path | Use the typed SDK action. |
|
|
135
|
+
| Adding `/api` to EAX URL | EAX base is not the REST base; app code should not build it. |
|
|
136
|
+
| Using `onStreamChunk`/`onTextChunk` | Use `onMessageChunk`. |
|
|
137
|
+
| Treating `run_error` as a successful result | Catch `WorkflowExecutionError` and inspect `outcome`. |
|
|
138
|
+
| Parsing `event.payload.result` | Read `event.message`; `run_complete` message is the final object. |
|
|
@@ -189,7 +189,7 @@ Schema:
|
|
|
189
189
|
"create": "scoped",
|
|
190
190
|
"update": "none",
|
|
191
191
|
"delete": "none",
|
|
192
|
-
"requiredScopes": ["
|
|
192
|
+
"requiredScopes": ["submissionKey"],
|
|
193
193
|
"allowedFilters": [],
|
|
194
194
|
"allowedSorts": [],
|
|
195
195
|
"defaultLimit": 1,
|
|
@@ -203,7 +203,7 @@ Frontend:
|
|
|
203
203
|
|
|
204
204
|
```ts
|
|
205
205
|
await howone.public.entities.Feedback.create({
|
|
206
|
-
|
|
206
|
+
submissionKey,
|
|
207
207
|
message,
|
|
208
208
|
rating,
|
|
209
209
|
})
|
|
@@ -211,7 +211,8 @@ await howone.public.entities.Feedback.create({
|
|
|
211
211
|
|
|
212
212
|
Rules:
|
|
213
213
|
|
|
214
|
-
- Public create
|
|
214
|
+
- Public create must use the declared public scope/business fields. Never pass ownership/system
|
|
215
|
+
fields such as `created_by_user_id`; the SDK rejects them and the backend derives ownership/scope.
|
|
215
216
|
- Do not expose public read unless needed.
|
|
216
217
|
- Add anti-abuse UX/server constraints outside the dynamic schema when needed.
|
|
217
218
|
- Never persist UI-only fields from form components.
|