@fayz-ai/sdk 0.9.0-next.1 → 0.10.0
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 +2 -2
- package/dist/ai-builder.js +1 -1
- package/dist/{chunk-2YJXKDHO.js → chunk-6UZOXHGJ.js} +11 -6
- package/dist/chunk-6UZOXHGJ.js.map +1 -0
- package/dist/{chunk-V4GWI7WH.js → chunk-7UXTYT6I.js} +65 -4
- package/dist/chunk-7UXTYT6I.js.map +1 -0
- package/dist/{chunk-7XM4LONE.js → chunk-BJFMBVMP.js} +2 -2
- package/dist/chunk-BJFMBVMP.js.map +1 -0
- package/dist/chunk-HA5O3CW7.js +181 -0
- package/dist/chunk-HA5O3CW7.js.map +1 -0
- package/dist/chunk-VRUPJGFZ.js +135 -0
- package/dist/chunk-VRUPJGFZ.js.map +1 -0
- package/dist/deprecations.d.ts +98 -0
- package/dist/deprecations.d.ts.map +1 -0
- package/dist/deprecations.js +3 -0
- package/dist/deprecations.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/release-channels.js +1 -1
- package/dist/shop.d.ts +32 -0
- package/dist/shop.d.ts.map +1 -1
- package/dist/shop.js +2 -1
- package/dist/supported-surface.d.ts +8 -1
- package/dist/supported-surface.d.ts.map +1 -1
- package/dist/supported-surface.js +1 -1
- package/dist/vite.d.ts +30 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +10 -2
- package/dist/vite.js.map +1 -1
- package/package.json +7 -14
- package/dist/chunk-2YJXKDHO.js.map +0 -1
- package/dist/chunk-7XM4LONE.js.map +0 -1
- package/dist/chunk-V4GWI7WH.js.map +0 -1
- package/dist/chunk-YZQW3BJA.js +0 -135
- package/dist/chunk-YZQW3BJA.js.map +0 -1
- package/src/agent.test.ts +0 -114
- package/src/agent.ts +0 -335
- package/src/ai-builder.ts +0 -115
- package/src/app-params.ts +0 -69
- package/src/client.test.ts +0 -176
- package/src/client.ts +0 -229
- package/src/index.ts +0 -94
- package/src/release-channels.json +0 -103
- package/src/release-channels.test.ts +0 -41
- package/src/release-channels.ts +0 -41
- package/src/runtime.test.ts +0 -123
- package/src/runtime.ts +0 -211
- package/src/shop.test.ts +0 -114
- package/src/shop.ts +0 -1118
- package/src/supported-surface.json +0 -40
- package/src/supported-surface.ts +0 -33
- package/src/types.ts +0 -67
- package/src/vite.ts +0 -121
package/src/agent.test.ts
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { createFayzAgentClient, FayzAgentError } from './agent'
|
|
4
|
-
|
|
5
|
-
function jsonResponse(body: unknown, status = 200): Response {
|
|
6
|
-
return new Response(JSON.stringify(body), {
|
|
7
|
-
status,
|
|
8
|
-
headers: { 'Content-Type': 'application/json' },
|
|
9
|
-
})
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function client(fetcher: typeof fetch) {
|
|
13
|
-
return createFayzAgentClient({
|
|
14
|
-
baseUrl: 'http://localhost:5173/api',
|
|
15
|
-
projectId: 'project-1',
|
|
16
|
-
publishableKey: 'fayzpk_test',
|
|
17
|
-
fetcher,
|
|
18
|
-
})
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
describe('createFayzAgentClient', () => {
|
|
22
|
-
it('targets the public broker and presents the publishable key', async () => {
|
|
23
|
-
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(
|
|
24
|
-
jsonResponse({ conversationId: 'conv-1', content: 'Hi', toolCalls: [] }),
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
await client(fetcher).chat({ message: 'Hello' })
|
|
28
|
-
|
|
29
|
-
const [url, init] = fetcher.mock.calls[0]
|
|
30
|
-
expect(url).toBe('http://localhost:5173/api/public/projects/project-1/agents/chat')
|
|
31
|
-
expect((init?.headers as Record<string, string>)['x-fayz-agent-key']).toBe('fayzpk_test')
|
|
32
|
-
expect(JSON.parse(init?.body as string)).toEqual({ message: 'Hello' })
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('forwards the tool catalog and returns the calls the model asked for', async () => {
|
|
36
|
-
const toolCall = { id: 'call-1', name: 'getTeamMembers', arguments: { role: 'staff' } }
|
|
37
|
-
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(
|
|
38
|
-
jsonResponse({ conversationId: 'conv-1', content: '', toolCalls: [toolCall] }),
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
const response = await client(fetcher).chat({
|
|
42
|
-
message: "Who's on my team?",
|
|
43
|
-
tools: [
|
|
44
|
-
{
|
|
45
|
-
name: 'getTeamMembers',
|
|
46
|
-
description: 'Lists team members.',
|
|
47
|
-
parameters: { type: 'object', properties: { role: { type: 'string' } } },
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
expect(response.toolCalls).toEqual([toolCall])
|
|
53
|
-
expect(JSON.parse(fetcher.mock.calls[0][1]?.body as string).tools).toHaveLength(1)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('sends tool results with the conversation id and no new message', async () => {
|
|
57
|
-
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(
|
|
58
|
-
jsonResponse({ conversationId: 'conv-1', content: 'Ana and Bruno.', toolCalls: [] }),
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
const response = await client(fetcher).chat({
|
|
62
|
-
conversationId: 'conv-1',
|
|
63
|
-
toolResults: [{ toolCallId: 'call-1', content: '{"total":2}' }],
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
const body = JSON.parse(fetcher.mock.calls[0][1]?.body as string)
|
|
67
|
-
expect(body).toEqual({
|
|
68
|
-
conversationId: 'conv-1',
|
|
69
|
-
toolResults: [{ toolCallId: 'call-1', content: '{"total":2}' }],
|
|
70
|
-
})
|
|
71
|
-
expect(response.content).toBe('Ana and Bruno.')
|
|
72
|
-
expect(response.toolCalls).toEqual([])
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('tolerates a response that omits toolCalls', async () => {
|
|
76
|
-
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(
|
|
77
|
-
jsonResponse({ conversationId: 'conv-1', content: 'Hi' }),
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
const response = await client(fetcher).chat({ message: 'Hello' })
|
|
81
|
-
|
|
82
|
-
expect(response.toolCalls).toEqual([])
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('surfaces the broker error message and status', async () => {
|
|
86
|
-
const fetcher = vi
|
|
87
|
-
.fn<typeof fetch>()
|
|
88
|
-
.mockResolvedValue(jsonResponse({ error: 'Invalid agent key' }, 401))
|
|
89
|
-
|
|
90
|
-
await expect(client(fetcher).chat({ message: 'Hello' })).rejects.toMatchObject({
|
|
91
|
-
name: 'FayzAgentError',
|
|
92
|
-
status: 401,
|
|
93
|
-
message: 'Invalid agent key',
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it('falls back to a generic message when the error body is not JSON', async () => {
|
|
98
|
-
const fetcher = vi
|
|
99
|
-
.fn<typeof fetch>()
|
|
100
|
-
.mockResolvedValue(new Response('gateway down', { status: 502 }))
|
|
101
|
-
|
|
102
|
-
await expect(client(fetcher).getInfo()).rejects.toThrow(FayzAgentError)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it('reads the agent identity from /config', async () => {
|
|
106
|
-
const agent = { id: 'a-1', name: 'Glow', description: null, icon: '💇' }
|
|
107
|
-
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(jsonResponse({ agent }))
|
|
108
|
-
|
|
109
|
-
expect(await client(fetcher).getInfo()).toEqual(agent)
|
|
110
|
-
expect(fetcher.mock.calls[0][0]).toBe(
|
|
111
|
-
'http://localhost:5173/api/public/projects/project-1/agents/config',
|
|
112
|
-
)
|
|
113
|
-
})
|
|
114
|
-
})
|
package/src/agent.ts
DELETED
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Client for the Fayz agent broker — the runtime surface a generated app uses
|
|
3
|
-
* to reach the AI agent configured for its project in Fayz.
|
|
4
|
-
*
|
|
5
|
-
* Mirrors `createFayzShopProvider`: the app holds a publishable key, Fayz holds
|
|
6
|
-
* the model credentials. The key ships in the bundle the same way a Supabase
|
|
7
|
-
* anon key does; it only ever resolves to the one agent its web channel belongs
|
|
8
|
-
* to, and the broker rate-limits per project.
|
|
9
|
-
*
|
|
10
|
-
* Tools are client-plane. The app declares what it can do (`tools`), the model
|
|
11
|
-
* decides what to call, and the app executes the calls against its own session
|
|
12
|
-
* before handing results back. Fayz never needs the app's data credentials.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
export interface FayzAgentClientOptions {
|
|
16
|
-
/** Fayz API base, e.g. https://fayz.ai/api */
|
|
17
|
-
baseUrl: string
|
|
18
|
-
projectId: string
|
|
19
|
-
/** Publishable key of the project's WEB agent channel. */
|
|
20
|
-
publishableKey: string
|
|
21
|
-
fetcher?: typeof fetch
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface FayzAgentTool {
|
|
25
|
-
name: string
|
|
26
|
-
description: string
|
|
27
|
-
/** JSON Schema for the tool's arguments. */
|
|
28
|
-
parameters?: Record<string, unknown>
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface FayzAgentToolCall {
|
|
32
|
-
id: string
|
|
33
|
-
name: string
|
|
34
|
-
arguments: Record<string, unknown>
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface FayzAgentToolResult {
|
|
38
|
-
toolCallId: string
|
|
39
|
-
content: string
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* A file the user attached, described rather than uploaded.
|
|
44
|
-
*
|
|
45
|
-
* The bytes stay in the browser: a spreadsheet of clients or patients is
|
|
46
|
-
* personal data, and handing it to a model provider so it can "take a look" is
|
|
47
|
-
* a disclosure, not a convenience. What travels is the shape — sheets,
|
|
48
|
-
* headers, how many rows, the inferred type per column and a handful of sample
|
|
49
|
-
* rows. Enough to plan an import; never the data set itself.
|
|
50
|
-
*/
|
|
51
|
-
export interface AgentAttachmentColumn {
|
|
52
|
-
name: string
|
|
53
|
-
type: 'text' | 'number' | 'date' | 'boolean' | 'email' | 'phone'
|
|
54
|
-
/** Literal shape when known ('dd/mm/yyyy', '1.234,56'). */
|
|
55
|
-
format?: string
|
|
56
|
-
/** Why the column is not the type it looks like (ambiguous dates, mixed values). */
|
|
57
|
-
note?: string
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface AgentAttachmentSheet {
|
|
61
|
-
name: string
|
|
62
|
-
headers: string[]
|
|
63
|
-
rowCount?: number
|
|
64
|
-
sample: string[][]
|
|
65
|
-
columnTypes: AgentAttachmentColumn[]
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface AgentAttachmentDigest {
|
|
69
|
-
id: string
|
|
70
|
-
name: string
|
|
71
|
-
mime: string
|
|
72
|
-
size: number
|
|
73
|
-
kind: 'spreadsheet' | 'csv' | 'text' | 'image' | 'pdf' | 'binary'
|
|
74
|
-
/** Multi-tab spreadsheets. Single-tab files use the flat fields below. */
|
|
75
|
-
sheets?: AgentAttachmentSheet[]
|
|
76
|
-
headers?: string[]
|
|
77
|
-
rowCount?: number
|
|
78
|
-
sample?: string[][]
|
|
79
|
-
columnTypes?: AgentAttachmentColumn[]
|
|
80
|
-
/** Head of a text/markdown file. */
|
|
81
|
-
preview?: string
|
|
82
|
-
/** What the reader had to guess at, truncate or give up on. */
|
|
83
|
-
warnings?: string[]
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface FayzAgentChatInput {
|
|
87
|
-
/** New end-user message. Omit when replying with tool results. */
|
|
88
|
-
message?: string
|
|
89
|
-
conversationId?: string
|
|
90
|
-
/** Identity of the end user, required by channels with an INTERNAL audience. */
|
|
91
|
-
externalUserId?: string
|
|
92
|
-
/** Human label for that user, so the owner's dashboard shows a name. */
|
|
93
|
-
externalUserName?: string
|
|
94
|
-
tools?: FayzAgentTool[]
|
|
95
|
-
toolResults?: FayzAgentToolResult[]
|
|
96
|
-
/** Decision for a server-parked write (broker PENDING_CONFIRMATION step). */
|
|
97
|
-
confirmAction?: { id: string; approved: boolean }
|
|
98
|
-
/** Runtime context the agent should know about (page, locale, tenant). */
|
|
99
|
-
context?: Record<string, unknown>
|
|
100
|
-
/** Files the user attached this turn, as digests — never as bytes. */
|
|
101
|
-
attachments?: AgentAttachmentDigest[]
|
|
102
|
-
signal?: AbortSignal
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** A server-plane write the broker parked pending human confirmation. */
|
|
106
|
-
export interface FayzAgentPendingAction {
|
|
107
|
-
id: string
|
|
108
|
-
toolName: string
|
|
109
|
-
title?: string
|
|
110
|
-
params?: Record<string, unknown>
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export interface FayzAgentChatResponse {
|
|
114
|
-
conversationId: string
|
|
115
|
-
content: string
|
|
116
|
-
toolCalls: FayzAgentToolCall[]
|
|
117
|
-
/** Present when the broker is waiting on a confirmAction for this write. */
|
|
118
|
-
pendingAction?: FayzAgentPendingAction
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export interface FayzAgentInfo {
|
|
122
|
-
id: string
|
|
123
|
-
name: string
|
|
124
|
-
description: string | null
|
|
125
|
-
icon: string | null
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export class FayzAgentError extends Error {
|
|
129
|
-
constructor(
|
|
130
|
-
message: string,
|
|
131
|
-
readonly status: number,
|
|
132
|
-
) {
|
|
133
|
-
super(message)
|
|
134
|
-
this.name = 'FayzAgentError'
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export interface FayzAgentConversationSummary {
|
|
139
|
-
id: string
|
|
140
|
-
title: string | null
|
|
141
|
-
/** Last activity — the field the list is ordered by. */
|
|
142
|
-
updatedAt: string
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export interface FayzAgentConversationMessage {
|
|
146
|
-
id: string
|
|
147
|
-
role: string
|
|
148
|
-
content: string
|
|
149
|
-
createdAt: string
|
|
150
|
-
/** Files the user attached to this turn, as the digest that was sent. Only
|
|
151
|
-
* the description — the bytes never left the browser that produced them. */
|
|
152
|
-
attachments?: AgentAttachmentDigest[]
|
|
153
|
-
/** Tool calls this reply made, with their results — so a resumed thread shows
|
|
154
|
-
* the same trace and record links it showed live. */
|
|
155
|
-
toolCalls?: Array<{
|
|
156
|
-
name: string
|
|
157
|
-
arguments?: Record<string, unknown>
|
|
158
|
-
result?: string
|
|
159
|
-
}>
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export interface FayzAgentConversationDetail {
|
|
163
|
-
id: string
|
|
164
|
-
title: string | null
|
|
165
|
-
messages: FayzAgentConversationMessage[]
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export interface FayzAgentStreamCallbacks {
|
|
169
|
-
/** Fired per text delta with the accumulated text so far (fill-in-place UIs). */
|
|
170
|
-
onDelta?: (accumulated: string, delta: string) => void
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export interface FayzAgentClient {
|
|
174
|
-
/** Identity of the agent behind the key, for rendering its name/icon. */
|
|
175
|
-
getInfo(signal?: AbortSignal): Promise<FayzAgentInfo>
|
|
176
|
-
chat(input: FayzAgentChatInput): Promise<FayzAgentChatResponse>
|
|
177
|
-
/**
|
|
178
|
-
* Like `chat`, but asks the broker for an SSE stream (`Accept:
|
|
179
|
-
* text/event-stream`) and reports text deltas as they arrive. Falls back
|
|
180
|
-
* transparently to the JSON round-trip when the broker predates streaming —
|
|
181
|
-
* callers can always use this and treat `onDelta` as best-effort.
|
|
182
|
-
*/
|
|
183
|
-
chatStream(input: FayzAgentChatInput, callbacks?: FayzAgentStreamCallbacks): Promise<FayzAgentChatResponse>
|
|
184
|
-
/** The signed-in user's own threads, most-recently-active first. */
|
|
185
|
-
listConversations(externalUserId?: string, signal?: AbortSignal): Promise<FayzAgentConversationSummary[]>
|
|
186
|
-
/** One thread with its transcript — 404s on another user's thread. */
|
|
187
|
-
getConversation(
|
|
188
|
-
conversationId: string,
|
|
189
|
-
externalUserId?: string,
|
|
190
|
-
signal?: AbortSignal,
|
|
191
|
-
): Promise<FayzAgentConversationDetail>
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function normalizeResponse(body: Partial<FayzAgentChatResponse>): FayzAgentChatResponse {
|
|
195
|
-
return {
|
|
196
|
-
conversationId: body.conversationId ?? '',
|
|
197
|
-
content: body.content ?? '',
|
|
198
|
-
toolCalls: body.toolCalls ?? [],
|
|
199
|
-
...(body.pendingAction ? { pendingAction: body.pendingAction } : {}),
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export function createFayzAgentClient(options: FayzAgentClientOptions): FayzAgentClient {
|
|
204
|
-
const fetcher = options.fetcher ?? globalThis.fetch
|
|
205
|
-
const base = `${options.baseUrl.replace(/\/+$/, '')}/public/projects/${options.projectId}/agents`
|
|
206
|
-
const headers = {
|
|
207
|
-
'Content-Type': 'application/json',
|
|
208
|
-
'x-fayz-agent-key': options.publishableKey,
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
async function request<T>(path: string, init: RequestInit): Promise<T> {
|
|
212
|
-
const response = await fetcher(`${base}${path}`, { ...init, headers })
|
|
213
|
-
if (!response.ok) {
|
|
214
|
-
const body = (await response.json().catch(() => null)) as { error?: string } | null
|
|
215
|
-
throw new FayzAgentError(body?.error ?? `Agent request failed (${response.status})`, response.status)
|
|
216
|
-
}
|
|
217
|
-
return (await response.json()) as T
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return {
|
|
221
|
-
async getInfo(signal) {
|
|
222
|
-
const body = await request<{ agent: FayzAgentInfo }>('/config', { method: 'GET', signal })
|
|
223
|
-
return body.agent
|
|
224
|
-
},
|
|
225
|
-
|
|
226
|
-
async chat({ signal, ...input }) {
|
|
227
|
-
const body = await request<Partial<FayzAgentChatResponse>>('/chat', {
|
|
228
|
-
method: 'POST',
|
|
229
|
-
body: JSON.stringify(input),
|
|
230
|
-
signal,
|
|
231
|
-
})
|
|
232
|
-
return normalizeResponse(body)
|
|
233
|
-
},
|
|
234
|
-
|
|
235
|
-
async chatStream({ signal, ...input }, callbacks) {
|
|
236
|
-
const response = await fetcher(`${base}/chat`, {
|
|
237
|
-
method: 'POST',
|
|
238
|
-
body: JSON.stringify(input),
|
|
239
|
-
signal,
|
|
240
|
-
headers: { ...headers, Accept: 'text/event-stream' },
|
|
241
|
-
})
|
|
242
|
-
if (!response.ok) {
|
|
243
|
-
const body = (await response.json().catch(() => null)) as { error?: string } | null
|
|
244
|
-
throw new FayzAgentError(body?.error ?? `Agent request failed (${response.status})`, response.status)
|
|
245
|
-
}
|
|
246
|
-
// Broker predates streaming (or chose not to): plain JSON round-trip.
|
|
247
|
-
const contentType = response.headers.get('content-type') ?? ''
|
|
248
|
-
if (!contentType.includes('text/event-stream') || !response.body) {
|
|
249
|
-
return normalizeResponse((await response.json()) as Partial<FayzAgentChatResponse>)
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const reader = response.body.getReader()
|
|
253
|
-
const decoder = new TextDecoder()
|
|
254
|
-
let buffer = ''
|
|
255
|
-
let accumulated = ''
|
|
256
|
-
let done: Partial<FayzAgentChatResponse> | null = null
|
|
257
|
-
|
|
258
|
-
const dispatch = (eventName: string, data: string) => {
|
|
259
|
-
if (!data) return
|
|
260
|
-
let payload: Record<string, unknown>
|
|
261
|
-
try {
|
|
262
|
-
payload = JSON.parse(data) as Record<string, unknown>
|
|
263
|
-
} catch {
|
|
264
|
-
return
|
|
265
|
-
}
|
|
266
|
-
if (eventName === 'delta') {
|
|
267
|
-
const text = typeof payload.text === 'string' ? payload.text : ''
|
|
268
|
-
if (text) {
|
|
269
|
-
accumulated += text
|
|
270
|
-
callbacks?.onDelta?.(accumulated, text)
|
|
271
|
-
}
|
|
272
|
-
} else if (eventName === 'done') {
|
|
273
|
-
done = payload as Partial<FayzAgentChatResponse>
|
|
274
|
-
} else if (eventName === 'error') {
|
|
275
|
-
throw new FayzAgentError(
|
|
276
|
-
typeof payload.error === 'string' ? payload.error : 'Agent stream failed',
|
|
277
|
-
response.status,
|
|
278
|
-
)
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
let eventName = 'message'
|
|
283
|
-
let dataLines: string[] = []
|
|
284
|
-
const processLine = (line: string) => {
|
|
285
|
-
if (line === '') {
|
|
286
|
-
dispatch(eventName, dataLines.join('\n'))
|
|
287
|
-
eventName = 'message'
|
|
288
|
-
dataLines = []
|
|
289
|
-
} else if (line.startsWith('event:')) {
|
|
290
|
-
eventName = line.slice(6).trim()
|
|
291
|
-
} else if (line.startsWith('data:')) {
|
|
292
|
-
dataLines.push(line.slice(5).trimStart())
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
for (;;) {
|
|
297
|
-
const { value, done: streamDone } = await reader.read()
|
|
298
|
-
if (streamDone) break
|
|
299
|
-
buffer += decoder.decode(value, { stream: true })
|
|
300
|
-
let newline = buffer.indexOf('\n')
|
|
301
|
-
while (newline >= 0) {
|
|
302
|
-
processLine(buffer.slice(0, newline).replace(/\r$/, ''))
|
|
303
|
-
buffer = buffer.slice(newline + 1)
|
|
304
|
-
newline = buffer.indexOf('\n')
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
processLine(buffer.replace(/\r$/, ''))
|
|
308
|
-
processLine('')
|
|
309
|
-
|
|
310
|
-
// The final `done` event carries the same payload shape as the JSON
|
|
311
|
-
// route; accumulated deltas back-fill `content` if the broker omits it.
|
|
312
|
-
const finalPayload: Partial<FayzAgentChatResponse> = { ...(done ?? {}) }
|
|
313
|
-
if (!finalPayload.content) finalPayload.content = accumulated
|
|
314
|
-
return normalizeResponse(finalPayload)
|
|
315
|
-
},
|
|
316
|
-
|
|
317
|
-
async listConversations(externalUserId, signal) {
|
|
318
|
-
const query = externalUserId ? `?externalUserId=${encodeURIComponent(externalUserId)}` : ''
|
|
319
|
-
const body = await request<{ conversations?: FayzAgentConversationSummary[] }>(
|
|
320
|
-
`/conversations${query}`,
|
|
321
|
-
{ method: 'GET', signal },
|
|
322
|
-
)
|
|
323
|
-
return body.conversations ?? []
|
|
324
|
-
},
|
|
325
|
-
|
|
326
|
-
async getConversation(conversationId, externalUserId, signal) {
|
|
327
|
-
const query = externalUserId ? `?externalUserId=${encodeURIComponent(externalUserId)}` : ''
|
|
328
|
-
const body = await request<{ conversation: FayzAgentConversationDetail }>(
|
|
329
|
-
`/conversations/${encodeURIComponent(conversationId)}${query}`,
|
|
330
|
-
{ method: 'GET', signal },
|
|
331
|
-
)
|
|
332
|
-
return body.conversation
|
|
333
|
-
},
|
|
334
|
-
}
|
|
335
|
-
}
|
package/src/ai-builder.ts
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
// The AI Builder request taxonomy — the contract the platform's request
|
|
2
|
-
// classifier targets. The classifier itself lives in the `fayz` platform repo;
|
|
3
|
-
// this is the canonical source of truth for the request classes and the
|
|
4
|
-
// architecture layer each is allowed to touch (docs/ARCHITECTURE.md).
|
|
5
|
-
|
|
6
|
-
/** Ownership layer a request may modify. */
|
|
7
|
-
export type FayzLayer = 'app' | 'plugin-config' | 'private-extension' | 'platform' | 'none'
|
|
8
|
-
|
|
9
|
-
export type AiBuilderRequestClass =
|
|
10
|
-
| 'app-edit'
|
|
11
|
-
| 'plugin-config'
|
|
12
|
-
| 'private-extension'
|
|
13
|
-
| 'platform-or-plugin-upgrade'
|
|
14
|
-
| 'unsafe-blocked'
|
|
15
|
-
|
|
16
|
-
export interface AiBuilderRequestClassDef {
|
|
17
|
-
id: AiBuilderRequestClass
|
|
18
|
-
label: string
|
|
19
|
-
/** Which ownership layer this class is allowed to modify. */
|
|
20
|
-
layer: FayzLayer
|
|
21
|
-
/** Who may perform it. */
|
|
22
|
-
editableBy: 'ai' | 'partner' | 'fayz' | 'none'
|
|
23
|
-
needsCode: boolean
|
|
24
|
-
needsDeploy: boolean
|
|
25
|
-
description: string
|
|
26
|
-
examples: string[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const AI_BUILDER_REQUEST_CLASSES: Record<AiBuilderRequestClass, AiBuilderRequestClassDef> = {
|
|
30
|
-
'app-edit': {
|
|
31
|
-
id: 'app-edit',
|
|
32
|
-
label: 'App edit',
|
|
33
|
-
layer: 'app',
|
|
34
|
-
editableBy: 'ai',
|
|
35
|
-
needsCode: false,
|
|
36
|
-
needsDeploy: false,
|
|
37
|
-
description:
|
|
38
|
-
'Change the generated app: pages, routes, layout, theme, copy, blocks, slots. Manifest-level edits (ladder 1–4) need no code; custom components/pages (ladder 5–6) are app-owned code.',
|
|
39
|
-
examples: [
|
|
40
|
-
'Change the brand color and headline',
|
|
41
|
-
'Add a new landing page with a hero and product grid',
|
|
42
|
-
'Reorder dashboard cards',
|
|
43
|
-
],
|
|
44
|
-
},
|
|
45
|
-
'plugin-config': {
|
|
46
|
-
id: 'plugin-config',
|
|
47
|
-
label: 'Plugin config',
|
|
48
|
-
layer: 'plugin-config',
|
|
49
|
-
editableBy: 'ai',
|
|
50
|
-
needsCode: false,
|
|
51
|
-
needsDeploy: false,
|
|
52
|
-
description:
|
|
53
|
-
'Configure an installed plugin as manifest data: enabled modules, fields, statuses, rules, feature flags, labels, currency.',
|
|
54
|
-
examples: [
|
|
55
|
-
'Enable the reconciliation module in financial',
|
|
56
|
-
'Add a "VIP" status to clients',
|
|
57
|
-
'Turn on delivery pricing in the menu',
|
|
58
|
-
],
|
|
59
|
-
},
|
|
60
|
-
'private-extension': {
|
|
61
|
-
id: 'private-extension',
|
|
62
|
-
label: 'Private extension',
|
|
63
|
-
layer: 'private-extension',
|
|
64
|
-
editableBy: 'partner',
|
|
65
|
-
needsCode: true,
|
|
66
|
-
needsDeploy: true,
|
|
67
|
-
description:
|
|
68
|
-
'Add new business behaviour as an app-local plugin (own entities, data, migrations, AI tools, connectors). The sanctioned partner/Silvio path — scaffold with `fayz create plugin`. See CUSTOMIZATION.md.',
|
|
69
|
-
examples: [
|
|
70
|
-
'Build a loyalty-points engine with its own table',
|
|
71
|
-
'Add a custom importer for a legacy ERP',
|
|
72
|
-
'Integrate a provider Fayz does not support yet (adapter)',
|
|
73
|
-
],
|
|
74
|
-
},
|
|
75
|
-
'platform-or-plugin-upgrade': {
|
|
76
|
-
id: 'platform-or-plugin-upgrade',
|
|
77
|
-
label: 'Platform / plugin upgrade',
|
|
78
|
-
layer: 'platform',
|
|
79
|
-
editableBy: 'fayz',
|
|
80
|
-
needsCode: true,
|
|
81
|
-
needsDeploy: true,
|
|
82
|
-
description:
|
|
83
|
-
'Change owned by Fayz: bump SDK/plugin versions, run manifest/DB migrations, evolve an engine. Not editable from a generated app.',
|
|
84
|
-
examples: [
|
|
85
|
-
'Upgrade @fayz-ai/core across the fleet',
|
|
86
|
-
'Migrate manifests v2 → v3',
|
|
87
|
-
'Add a capability to an official plugin',
|
|
88
|
-
],
|
|
89
|
-
},
|
|
90
|
-
'unsafe-blocked': {
|
|
91
|
-
id: 'unsafe-blocked',
|
|
92
|
-
label: 'Unsafe / blocked',
|
|
93
|
-
layer: 'none',
|
|
94
|
-
editableBy: 'none',
|
|
95
|
-
needsCode: false,
|
|
96
|
-
needsDeploy: false,
|
|
97
|
-
description:
|
|
98
|
-
'Requests that would break the boundary and must be refused or rerouted: mutating SDK/runtime/plugin internals, forking an SDK page, importing a provider SDK directly into app code, or bypassing tenancy/permissions/security.',
|
|
99
|
-
examples: [
|
|
100
|
-
'Edit the code inside @fayz-ai/plugin-crm',
|
|
101
|
-
'Copy an SDK page into the app and modify it',
|
|
102
|
-
'Call the Supabase client directly from a storefront component',
|
|
103
|
-
'Disable row-level security for a tenant',
|
|
104
|
-
],
|
|
105
|
-
},
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export const AI_BUILDER_REQUEST_CLASS_IDS = Object.keys(
|
|
109
|
-
AI_BUILDER_REQUEST_CLASSES,
|
|
110
|
-
) as AiBuilderRequestClass[]
|
|
111
|
-
|
|
112
|
-
/** True when a request class is permitted (anything but unsafe-blocked). */
|
|
113
|
-
export function isAllowedRequestClass(id: AiBuilderRequestClass): boolean {
|
|
114
|
-
return id !== 'unsafe-blocked'
|
|
115
|
-
}
|
package/src/app-params.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { FayzAppParams, FayzRuntimeEnvironment } from './types'
|
|
2
|
-
|
|
3
|
-
function readSearchParams(): URLSearchParams {
|
|
4
|
-
const href = globalThis.location?.href
|
|
5
|
-
return href ? new URL(href).searchParams : new URLSearchParams()
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function readMeta(name: string): string | undefined {
|
|
9
|
-
const documentRef = globalThis.document
|
|
10
|
-
const value = documentRef?.querySelector<HTMLMetaElement>(`meta[name="${name}"]`)?.content?.trim()
|
|
11
|
-
return value || undefined
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function readEnv(name: string): string | undefined {
|
|
15
|
-
const env = (import.meta as unknown as { env?: Record<string, string | undefined> }).env
|
|
16
|
-
return env?.[name]?.trim() || undefined
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function firstValue(...values: Array<string | undefined>): string | undefined {
|
|
20
|
-
return values.find((value) => value && value.trim())?.trim()
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function normalizeEnvironment(value?: string): FayzRuntimeEnvironment {
|
|
24
|
-
return value === 'production' ? 'production' : 'preview'
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function resolveAppParams(): FayzAppParams {
|
|
28
|
-
const search = readSearchParams()
|
|
29
|
-
const token = firstValue(
|
|
30
|
-
search.get('token') ?? undefined,
|
|
31
|
-
search.get('fayz_token') ?? undefined,
|
|
32
|
-
readMeta('fayz-token'),
|
|
33
|
-
readEnv('VITE_FAYZ_TOKEN'),
|
|
34
|
-
)
|
|
35
|
-
const appId = firstValue(
|
|
36
|
-
search.get('appId') ?? undefined,
|
|
37
|
-
search.get('app_id') ?? undefined,
|
|
38
|
-
readMeta('fayz-app-id'),
|
|
39
|
-
readEnv('VITE_FAYZ_APP_ID'),
|
|
40
|
-
)
|
|
41
|
-
const projectId = firstValue(
|
|
42
|
-
search.get('projectId') ?? undefined,
|
|
43
|
-
search.get('project_id') ?? undefined,
|
|
44
|
-
readMeta('fayz-project-id'),
|
|
45
|
-
readEnv('VITE_FAYZ_PROJECT_ID'),
|
|
46
|
-
)
|
|
47
|
-
const apiBaseUrl = firstValue(
|
|
48
|
-
search.get('apiBaseUrl') ?? undefined,
|
|
49
|
-
search.get('api_base_url') ?? undefined,
|
|
50
|
-
readMeta('fayz-api-base-url'),
|
|
51
|
-
readEnv('VITE_FAYZ_API_BASE_URL'),
|
|
52
|
-
globalThis.location?.origin,
|
|
53
|
-
) ?? ''
|
|
54
|
-
const environment = normalizeEnvironment(firstValue(
|
|
55
|
-
search.get('environment') ?? undefined,
|
|
56
|
-
readMeta('fayz-environment'),
|
|
57
|
-
readEnv('VITE_FAYZ_ENVIRONMENT'),
|
|
58
|
-
))
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
apiBaseUrl,
|
|
62
|
-
environment,
|
|
63
|
-
...(appId ? { appId } : {}),
|
|
64
|
-
...(projectId ? { projectId } : {}),
|
|
65
|
-
...(token ? { token } : {}),
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export const appParams = resolveAppParams()
|