@ouraihub/aether-worker 0.0.0-stage → 0.1.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/LICENSE +789 -0
- package/migrations/0001.sql +22 -0
- package/package.json +37 -3
- package/src/app.ts +327 -0
- package/src/cloudflare.ts +49 -0
- package/src/context.ts +49 -0
- package/src/conversations/action.ts +234 -0
- package/src/conversations/index.ts +477 -0
- package/src/conversations/model.ts +402 -0
- package/src/coordinator.ts +560 -0
- package/src/credentials.ts +153 -0
- package/src/files.ts +105 -0
- package/src/host.ts +17 -0
- package/src/identity.ts +198 -0
- package/src/images/index.ts +46 -0
- package/src/index.ts +30 -0
- package/src/logger.ts +87 -0
- package/src/mcp/auth.ts +350 -0
- package/src/mcp/client.ts +426 -0
- package/src/mcp/common.ts +120 -0
- package/src/mcp/index.ts +239 -0
- package/src/mcp/oauth.ts +284 -0
- package/src/mcp/types.ts +74 -0
- package/src/outbound.ts +305 -0
- package/src/providers/index.ts +505 -0
- package/src/runtime.ts +635 -0
- package/src/search/index.ts +431 -0
- package/src/skills/index.ts +1 -0
- package/src/skills/persistence.ts +140 -0
- package/src/skills/service.ts +203 -0
- package/src/storage.ts +198 -0
- package/README.md +0 -3
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import type { ImagePart, ModelRef, RunRequest } from '@ouraihub/aether-contracts'
|
|
2
|
+
import type { HistoryTurn } from '@ouraihub/aether-core/types'
|
|
3
|
+
import { badRequest } from '@ouraihub/aether-core/error'
|
|
4
|
+
import { canonicalJson, charCount } from '@ouraihub/aether-core/guard'
|
|
5
|
+
import {
|
|
6
|
+
checkId,
|
|
7
|
+
checkText,
|
|
8
|
+
conflict,
|
|
9
|
+
historyPath,
|
|
10
|
+
idle,
|
|
11
|
+
imageParts,
|
|
12
|
+
notFound,
|
|
13
|
+
target,
|
|
14
|
+
validateImages,
|
|
15
|
+
visibleText,
|
|
16
|
+
} from './model.ts'
|
|
17
|
+
import type { ConversationDocument, StoredImage, StoredTurn } from './model.ts'
|
|
18
|
+
|
|
19
|
+
export type Action =
|
|
20
|
+
| { kind: 'reply'; parentId: string | null }
|
|
21
|
+
| { kind: 'retry'; sourceId: string }
|
|
22
|
+
| { kind: 'translation'; sourceId: string; targetLanguage: string }
|
|
23
|
+
export type SelectionGuard =
|
|
24
|
+
{ kind: 'explicit' } | { kind: 'selected'; turnId: string } | { kind: 'latest'; turnId: string | null }
|
|
25
|
+
export interface ReservationInput {
|
|
26
|
+
input: string
|
|
27
|
+
images: ImagePart[]
|
|
28
|
+
historyPath: StoredTurn[]
|
|
29
|
+
history: HistoryTurn[]
|
|
30
|
+
sourceModel: ModelRef | null
|
|
31
|
+
}
|
|
32
|
+
export interface PreparedAction extends ReservationInput {
|
|
33
|
+
request: RunRequest
|
|
34
|
+
effectiveRequest: RunRequest
|
|
35
|
+
action: Action
|
|
36
|
+
selectionGuard: SelectionGuard
|
|
37
|
+
expected: string
|
|
38
|
+
requiresSourceModel: boolean
|
|
39
|
+
}
|
|
40
|
+
function latest(document: ConversationDocument): string | null {
|
|
41
|
+
return document.turns.findLast((t) => t.status === 'completed')?.id ?? null
|
|
42
|
+
}
|
|
43
|
+
export function authoritative(
|
|
44
|
+
request: RunRequest,
|
|
45
|
+
action: Action,
|
|
46
|
+
guard: SelectionGuard,
|
|
47
|
+
document: ConversationDocument | null,
|
|
48
|
+
): ReservationInput {
|
|
49
|
+
if ((request.conversationId ?? null) !== (document?.summary.id ?? null)) throw notFound('conversation')
|
|
50
|
+
if (document) {
|
|
51
|
+
idle(document)
|
|
52
|
+
if (guard.kind === 'selected' && document.summary.selectedTurnId !== guard.turnId)
|
|
53
|
+
throw conflict('preparation_changed')
|
|
54
|
+
if (guard.kind === 'latest' && (document.summary.selectedTurnId !== null || latest(document) !== guard.turnId))
|
|
55
|
+
throw conflict('preparation_changed')
|
|
56
|
+
}
|
|
57
|
+
let input = request.input,
|
|
58
|
+
images = request.images ?? [],
|
|
59
|
+
head: string | null,
|
|
60
|
+
sourceModel: ModelRef | null = null
|
|
61
|
+
if (action.kind === 'reply') {
|
|
62
|
+
head = action.parentId
|
|
63
|
+
if (head !== null && (!document || target(document, head).status !== 'completed'))
|
|
64
|
+
throw conflict('turn_not_completed')
|
|
65
|
+
} else {
|
|
66
|
+
if (!document) throw notFound('conversation')
|
|
67
|
+
const source = target(document, action.sourceId)
|
|
68
|
+
if (source.status === 'running') throw conflict('turn_running')
|
|
69
|
+
sourceModel =
|
|
70
|
+
source.endpointId !== null && source.modelId !== null
|
|
71
|
+
? { endpointId: source.endpointId, modelId: source.modelId }
|
|
72
|
+
: null
|
|
73
|
+
if (action.kind === 'retry') {
|
|
74
|
+
input = source.input
|
|
75
|
+
images = imageParts(source.images)
|
|
76
|
+
head = source.parentId
|
|
77
|
+
} else {
|
|
78
|
+
const text = visibleText(source)
|
|
79
|
+
if (!text.trim()) throw badRequest('translateTurnId', 'empty_reply')
|
|
80
|
+
input = `Translate the following reply into ${action.targetLanguage}. Return only the translation, preserving Markdown formatting.\n\n${text}`
|
|
81
|
+
images = []
|
|
82
|
+
head = source.id
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
checkText('input', input, 100_000)
|
|
86
|
+
const path = document ? historyPath(document, head) : []
|
|
87
|
+
return {
|
|
88
|
+
input,
|
|
89
|
+
images,
|
|
90
|
+
historyPath: path,
|
|
91
|
+
history: path.map((turn) => ({
|
|
92
|
+
status: turn.status,
|
|
93
|
+
input: turn.input,
|
|
94
|
+
messages: turn.messages,
|
|
95
|
+
images: imageParts(turn.images),
|
|
96
|
+
})),
|
|
97
|
+
sourceModel,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function digest(value: string): Promise<string> {
|
|
101
|
+
const bytes = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value))
|
|
102
|
+
return Array.from(new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, '0')).join('')
|
|
103
|
+
}
|
|
104
|
+
async function imageBasis(turnId: string, kind: string, images: readonly StoredImage[]): Promise<unknown[]> {
|
|
105
|
+
return Promise.all(
|
|
106
|
+
images.map(async (image, index) => ({
|
|
107
|
+
turnId,
|
|
108
|
+
kind,
|
|
109
|
+
index,
|
|
110
|
+
mime: image.mime,
|
|
111
|
+
data: image.data === null ? null : await digest(image.data),
|
|
112
|
+
})),
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
export async function fingerprint(
|
|
116
|
+
request: RunRequest,
|
|
117
|
+
action: Action,
|
|
118
|
+
guard: SelectionGuard,
|
|
119
|
+
document: ConversationDocument | null,
|
|
120
|
+
input: ReservationInput,
|
|
121
|
+
): Promise<string> {
|
|
122
|
+
const source = action.kind !== 'reply' && document ? target(document, action.sourceId) : null
|
|
123
|
+
const sourceBasis = source
|
|
124
|
+
? {
|
|
125
|
+
id: source.id,
|
|
126
|
+
status: source.status,
|
|
127
|
+
parentId: source.parentId,
|
|
128
|
+
questionId: source.questionId,
|
|
129
|
+
supersedesId: source.supersedesId,
|
|
130
|
+
kind: source.kind,
|
|
131
|
+
modelId: source.modelId,
|
|
132
|
+
endpointId: source.endpointId,
|
|
133
|
+
input: source.input,
|
|
134
|
+
visibleText: action.kind === 'translation' ? visibleText(source) : null,
|
|
135
|
+
images: await imageBasis(source.id, 'input', source.images),
|
|
136
|
+
generated: await imageBasis(source.id, 'generated', source.generated),
|
|
137
|
+
}
|
|
138
|
+
: null
|
|
139
|
+
const path = await Promise.all(
|
|
140
|
+
input.historyPath.map(async (turn) => ({
|
|
141
|
+
id: turn.id,
|
|
142
|
+
parentId: turn.parentId,
|
|
143
|
+
status: turn.status,
|
|
144
|
+
input: turn.input,
|
|
145
|
+
messages: turn.messages,
|
|
146
|
+
endpointId: turn.endpointId,
|
|
147
|
+
modelId: turn.modelId,
|
|
148
|
+
reasoningEffort: turn.reasoningEffort,
|
|
149
|
+
images: await imageBasis(turn.id, 'input', turn.images),
|
|
150
|
+
generated: await imageBasis(turn.id, 'generated', turn.generated),
|
|
151
|
+
})),
|
|
152
|
+
)
|
|
153
|
+
const currentImages = await Promise.all(
|
|
154
|
+
input.images.map(async (image, index) => ({ index, mime: image.mime, data: await digest(image.data) })),
|
|
155
|
+
)
|
|
156
|
+
return digest(
|
|
157
|
+
canonicalJson({
|
|
158
|
+
version: 1,
|
|
159
|
+
conversationId: request.conversationId ?? null,
|
|
160
|
+
scope: document?.summary.scope ?? request.scope ?? null,
|
|
161
|
+
action,
|
|
162
|
+
guard,
|
|
163
|
+
input: input.input,
|
|
164
|
+
images: currentImages,
|
|
165
|
+
source: sourceBasis,
|
|
166
|
+
path,
|
|
167
|
+
}),
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
export async function resolveAction(
|
|
171
|
+
request: RunRequest,
|
|
172
|
+
document: ConversationDocument | null,
|
|
173
|
+
): Promise<PreparedAction> {
|
|
174
|
+
for (const [field, id] of [
|
|
175
|
+
['conversationId', request.conversationId],
|
|
176
|
+
['replyToId', request.replyToId],
|
|
177
|
+
['retryTurnId', request.retryTurnId],
|
|
178
|
+
['translateTurnId', request.translateTurnId],
|
|
179
|
+
])
|
|
180
|
+
if (id !== undefined && id !== null) {
|
|
181
|
+
if (typeof id !== 'string') throw badRequest(String(field), 'invalid')
|
|
182
|
+
checkId(String(field), id)
|
|
183
|
+
}
|
|
184
|
+
if (request.scope !== undefined && request.scope !== null) checkText('scope', request.scope, 200)
|
|
185
|
+
checkText('input', request.input, 100_000)
|
|
186
|
+
validateImages(request.images ?? [])
|
|
187
|
+
const sourced = Boolean(request.retryTurnId || request.translateTurnId || request.regenerate)
|
|
188
|
+
if (Number(Boolean(request.retryTurnId)) + Number(Boolean(request.translateTurnId)) + Number(request.regenerate) > 1)
|
|
189
|
+
throw badRequest('action', 'multiple')
|
|
190
|
+
if (sourced && request.replyToId) throw badRequest('replyToId', 'action_conflict')
|
|
191
|
+
if (sourced && (request.images?.length ?? 0) > 0) throw badRequest('images', 'action_conflict')
|
|
192
|
+
if (Boolean(request.translateTurnId) !== (request.targetLanguage !== null && request.targetLanguage !== undefined))
|
|
193
|
+
throw badRequest('targetLanguage', 'translation_required')
|
|
194
|
+
if (!request.conversationId && (sourced || request.replyToId)) throw badRequest('conversationId', 'required')
|
|
195
|
+
if ((request.retryTurnId || request.translateTurnId) && request.webSearch)
|
|
196
|
+
throw badRequest('webSearch', 'action_conflict')
|
|
197
|
+
let guard: SelectionGuard = { kind: 'explicit' },
|
|
198
|
+
head: string | null = request.replyToId ?? null
|
|
199
|
+
if (document && !request.replyToId && !request.retryTurnId && !request.translateTurnId) {
|
|
200
|
+
if (document.summary.selectedTurnId !== null) {
|
|
201
|
+
head = document.summary.selectedTurnId
|
|
202
|
+
guard = { kind: 'selected', turnId: head }
|
|
203
|
+
} else {
|
|
204
|
+
head = latest(document)
|
|
205
|
+
guard = { kind: 'latest', turnId: head }
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
let action: Action
|
|
209
|
+
if (request.translateTurnId) {
|
|
210
|
+
const language = request.targetLanguage?.trim() ?? ''
|
|
211
|
+
if (!language || charCount(language) > 80) throw badRequest('targetLanguage', 'invalid')
|
|
212
|
+
action = { kind: 'translation', sourceId: request.translateTurnId, targetLanguage: language }
|
|
213
|
+
} else if (request.retryTurnId) action = { kind: 'retry', sourceId: request.retryTurnId }
|
|
214
|
+
else if (request.regenerate) {
|
|
215
|
+
if (head === null) throw notFound('turn')
|
|
216
|
+
action = { kind: 'retry', sourceId: head }
|
|
217
|
+
} else action = { kind: 'reply', parentId: head }
|
|
218
|
+
const input = authoritative(request, action, guard, document)
|
|
219
|
+
const effectiveRequest: RunRequest = {
|
|
220
|
+
...request,
|
|
221
|
+
input: input.input,
|
|
222
|
+
images: input.images,
|
|
223
|
+
...(request.model ? {} : { model: input.sourceModel }),
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
...input,
|
|
227
|
+
request,
|
|
228
|
+
effectiveRequest,
|
|
229
|
+
action,
|
|
230
|
+
selectionGuard: guard,
|
|
231
|
+
expected: await fingerprint(request, action, guard, document, input),
|
|
232
|
+
requiresSourceModel: action.kind !== 'reply' && !request.model,
|
|
233
|
+
}
|
|
234
|
+
}
|