digital-workers 2.1.3 → 2.3.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/CHANGELOG.md +9 -0
- package/README.md +2 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +33 -21
- package/dist/actions.js.map +1 -1
- package/dist/agent-comms.d.ts.map +1 -1
- package/dist/agent-comms.js +36 -25
- package/dist/agent-comms.js.map +1 -1
- package/dist/approve.d.ts +40 -8
- package/dist/approve.d.ts.map +1 -1
- package/dist/approve.js +86 -20
- package/dist/approve.js.map +1 -1
- package/dist/ask.d.ts +38 -7
- package/dist/ask.d.ts.map +1 -1
- package/dist/ask.js +85 -25
- package/dist/ask.js.map +1 -1
- package/dist/browse.d.ts +223 -0
- package/dist/browse.d.ts.map +1 -0
- package/dist/browse.js +392 -0
- package/dist/browse.js.map +1 -0
- package/dist/capability-tiers.js +3 -3
- package/dist/capability-tiers.js.map +1 -1
- package/dist/cascade-context.d.ts +28 -28
- package/dist/client.d.ts +162 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +64 -0
- package/dist/client.js.map +1 -0
- package/dist/decide.d.ts +42 -6
- package/dist/decide.d.ts.map +1 -1
- package/dist/decide.js +54 -11
- package/dist/decide.js.map +1 -1
- package/dist/do.d.ts +36 -7
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +82 -39
- package/dist/do.js.map +1 -1
- package/dist/error-escalation.d.ts.map +1 -1
- package/dist/error-escalation.js +38 -38
- package/dist/error-escalation.js.map +1 -1
- package/dist/generate.d.ts +48 -7
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +49 -8
- package/dist/generate.js.map +1 -1
- package/dist/goals.d.ts +10 -9
- package/dist/goals.d.ts.map +1 -1
- package/dist/goals.js +30 -24
- package/dist/goals.js.map +1 -1
- package/dist/image.d.ts +189 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +528 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +49 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -2
- package/dist/index.js.map +1 -1
- package/dist/is.d.ts +45 -10
- package/dist/is.d.ts.map +1 -1
- package/dist/is.js +56 -21
- package/dist/is.js.map +1 -1
- package/dist/kpis.d.ts +24 -15
- package/dist/kpis.d.ts.map +1 -1
- package/dist/kpis.js +16 -14
- package/dist/kpis.js.map +1 -1
- package/dist/load-balancing.d.ts.map +1 -1
- package/dist/load-balancing.js +124 -38
- package/dist/load-balancing.js.map +1 -1
- package/dist/logger.d.ts +76 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +39 -0
- package/dist/logger.js.map +1 -0
- package/dist/notify.d.ts +38 -9
- package/dist/notify.d.ts.map +1 -1
- package/dist/notify.js +72 -17
- package/dist/notify.js.map +1 -1
- package/dist/role.d.ts +5 -4
- package/dist/role.d.ts.map +1 -1
- package/dist/role.js +13 -10
- package/dist/role.js.map +1 -1
- package/dist/runtime.d.ts +310 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +510 -0
- package/dist/runtime.js.map +1 -0
- package/dist/team.d.ts +11 -6
- package/dist/team.d.ts.map +1 -1
- package/dist/team.js +22 -15
- package/dist/team.js.map +1 -1
- package/dist/transports/email.d.ts +318 -0
- package/dist/transports/email.d.ts.map +1 -0
- package/dist/transports/email.js +779 -0
- package/dist/transports/email.js.map +1 -0
- package/dist/transports/slack.d.ts +515 -0
- package/dist/transports/slack.d.ts.map +1 -0
- package/dist/transports/slack.js +844 -0
- package/dist/transports/slack.js.map +1 -0
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +44 -25
- package/dist/transports.js.map +1 -1
- package/dist/types.d.ts +141 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/id.d.ts +19 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +21 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/video.d.ts +203 -0
- package/dist/video.d.ts.map +1 -0
- package/dist/video.js +528 -0
- package/dist/video.js.map +1 -0
- package/dist/worker.d.ts +343 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +698 -0
- package/dist/worker.js.map +1 -0
- package/package.json +32 -14
- package/src/actions.ts +39 -30
- package/src/agent-comms.ts +54 -92
- package/src/approve.ts +91 -20
- package/src/ask.ts +99 -25
- package/src/browse.ts +627 -0
- package/src/capability-tiers.ts +5 -5
- package/src/client.ts +221 -0
- package/src/decide.ts +81 -35
- package/src/do.ts +98 -52
- package/src/error-escalation.ts +55 -67
- package/src/generate.ts +52 -18
- package/src/goals.ts +36 -27
- package/src/image.ts +816 -0
- package/src/index.ts +187 -2
- package/src/is.ts +59 -25
- package/src/kpis.ts +41 -36
- package/src/load-balancing.ts +132 -46
- package/src/logger.ts +93 -0
- package/src/notify.ts +78 -17
- package/src/role.ts +30 -20
- package/src/runtime.ts +796 -0
- package/src/team.ts +24 -19
- package/src/transports/email.ts +1160 -0
- package/src/transports/slack.ts +1320 -0
- package/src/transports.ts +58 -43
- package/src/types.ts +174 -46
- package/src/utils/id.ts +21 -0
- package/src/video.ts +906 -0
- package/src/worker.ts +1007 -0
- package/test/approve.test.ts +305 -0
- package/test/ask.test.ts +274 -0
- package/test/browse.test.ts +361 -0
- package/test/decide.test.ts +252 -0
- package/test/do.test.ts +144 -0
- package/test/error-logging.test.ts +357 -0
- package/test/generate.test.ts +319 -0
- package/test/image.test.ts +398 -0
- package/test/is.test.ts +287 -0
- package/test/load-balancing-safety.test.ts +404 -0
- package/test/notify.test.ts +434 -0
- package/test/primitives.test.ts +320 -0
- package/test/runtime-integration.test.ts +892 -0
- package/test/transports/crypto.test.ts +230 -0
- package/test/transports/email.test.ts +866 -0
- package/test/transports/id-generation.test.ts +91 -0
- package/test/transports/slack.test.ts +760 -0
- package/test/type-safety.test.ts +834 -0
- package/test/types.test.ts +60 -2
- package/test/video.test.ts +530 -0
- package/test/worker.test.ts +1433 -0
- package/tsconfig.json +4 -1
- package/vitest.config.ts +42 -0
- package/wrangler.jsonc +36 -0
- package/.turbo/turbo-build.log +0 -4
- package/LICENSE +0 -21
- package/src/actions.js +0 -436
- package/src/approve.js +0 -234
- package/src/ask.js +0 -226
- package/src/decide.js +0 -244
- package/src/do.js +0 -227
- package/src/generate.js +0 -298
- package/src/goals.js +0 -205
- package/src/index.js +0 -68
- package/src/is.js +0 -317
- package/src/kpis.js +0 -270
- package/src/notify.js +0 -219
- package/src/role.js +0 -110
- package/src/team.js +0 -130
- package/src/transports.js +0 -357
- package/src/types.js +0 -71
package/src/worker.ts
ADDED
|
@@ -0,0 +1,1007 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker Export - WorkerEntrypoint for RPC access to Digital Workers
|
|
3
|
+
*
|
|
4
|
+
* Exposes worker lifecycle management, messaging, and coordination via Cloudflare RPC.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* // wrangler.jsonc
|
|
9
|
+
* {
|
|
10
|
+
* "services": [
|
|
11
|
+
* { "binding": "DIGITAL_WORKERS", "service": "digital-workers" }
|
|
12
|
+
* ]
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* // worker.ts - consuming service
|
|
16
|
+
* export default {
|
|
17
|
+
* async fetch(request: Request, env: Env) {
|
|
18
|
+
* const service = env.DIGITAL_WORKERS.connect()
|
|
19
|
+
* const worker = await service.spawn({ name: 'my-worker' })
|
|
20
|
+
* return Response.json(worker)
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @packageDocumentation
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { WorkerEntrypoint, RpcTarget } from 'cloudflare:workers'
|
|
29
|
+
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// Types
|
|
32
|
+
// =============================================================================
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Worker instance status
|
|
36
|
+
*/
|
|
37
|
+
type WorkerInstanceStatus = 'spawning' | 'running' | 'paused' | 'terminated' | 'error'
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Worker instance state
|
|
41
|
+
*/
|
|
42
|
+
interface WorkerInstance {
|
|
43
|
+
id: string
|
|
44
|
+
name: string
|
|
45
|
+
status: WorkerInstanceStatus
|
|
46
|
+
type: 'agent' | 'human'
|
|
47
|
+
tier?: string | undefined
|
|
48
|
+
createdAt: Date
|
|
49
|
+
updatedAt: Date
|
|
50
|
+
metadata?: Record<string, unknown> | undefined
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Message sent between workers
|
|
55
|
+
*/
|
|
56
|
+
interface WorkerMessage<T = unknown> {
|
|
57
|
+
id: string
|
|
58
|
+
from: string
|
|
59
|
+
to: string
|
|
60
|
+
type: string
|
|
61
|
+
payload: T
|
|
62
|
+
timestamp: Date
|
|
63
|
+
acknowledged?: boolean
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Worker spawn options
|
|
68
|
+
*/
|
|
69
|
+
interface SpawnOptions {
|
|
70
|
+
name?: string
|
|
71
|
+
type?: 'agent' | 'human'
|
|
72
|
+
tier?: string
|
|
73
|
+
metadata?: Record<string, unknown>
|
|
74
|
+
timeout?: number
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Receive options
|
|
79
|
+
*/
|
|
80
|
+
interface ReceiveOptions {
|
|
81
|
+
type?: string
|
|
82
|
+
limit?: number
|
|
83
|
+
acknowledged?: boolean
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* List options
|
|
88
|
+
*/
|
|
89
|
+
interface ListOptions {
|
|
90
|
+
status?: WorkerInstanceStatus
|
|
91
|
+
type?: 'agent' | 'human'
|
|
92
|
+
tier?: string
|
|
93
|
+
limit?: number
|
|
94
|
+
includeTerminated?: boolean
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Set state options
|
|
99
|
+
*/
|
|
100
|
+
interface SetStateOptions {
|
|
101
|
+
metadata?: Record<string, unknown>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Broadcast result
|
|
106
|
+
*/
|
|
107
|
+
interface BroadcastResult {
|
|
108
|
+
workerId: string
|
|
109
|
+
success: boolean
|
|
110
|
+
messageId?: string | undefined
|
|
111
|
+
error?: string | undefined
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Coordination task
|
|
116
|
+
*/
|
|
117
|
+
interface CoordinationTask<T = unknown> {
|
|
118
|
+
id: string
|
|
119
|
+
type: 'fanout' | 'pipeline' | 'race' | 'consensus'
|
|
120
|
+
workers: string[]
|
|
121
|
+
status: 'pending' | 'running' | 'completed' | 'failed'
|
|
122
|
+
result?: T | undefined
|
|
123
|
+
errors?: Error[] | undefined
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Consensus options
|
|
128
|
+
*/
|
|
129
|
+
interface ConsensusOptions {
|
|
130
|
+
quorum?: number
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Notification target
|
|
135
|
+
*/
|
|
136
|
+
type NotificationTarget = string | string[] | { id: string; contacts: Record<string, unknown> }
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Notification options
|
|
140
|
+
*/
|
|
141
|
+
interface NotifyOptions {
|
|
142
|
+
target: NotificationTarget
|
|
143
|
+
message: string
|
|
144
|
+
via?: string
|
|
145
|
+
priority?: 'low' | 'normal' | 'high' | 'urgent'
|
|
146
|
+
metadata?: Record<string, unknown>
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Notification result
|
|
151
|
+
*/
|
|
152
|
+
interface NotifyResult {
|
|
153
|
+
sent: boolean
|
|
154
|
+
messageId: string
|
|
155
|
+
via: string[]
|
|
156
|
+
recipients?: string[]
|
|
157
|
+
sentAt: Date
|
|
158
|
+
jobId: string
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Decide options
|
|
163
|
+
*/
|
|
164
|
+
interface DecideOptions {
|
|
165
|
+
options: (string | { id?: string; label?: string; [key: string]: unknown })[]
|
|
166
|
+
context?: string
|
|
167
|
+
criteria?: string[]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Decision result
|
|
172
|
+
*/
|
|
173
|
+
interface DecisionResult {
|
|
174
|
+
choice: string | { id?: string; label?: string; [key: string]: unknown }
|
|
175
|
+
reasoning: string
|
|
176
|
+
confidence: number
|
|
177
|
+
alternatives?: {
|
|
178
|
+
option: string | { id?: string; label?: string; [key: string]: unknown }
|
|
179
|
+
score: number
|
|
180
|
+
}[]
|
|
181
|
+
jobId: string
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Ask AI options
|
|
186
|
+
*/
|
|
187
|
+
interface AskAIOptions {
|
|
188
|
+
context?: Record<string, unknown>
|
|
189
|
+
schema?: Record<string, unknown>
|
|
190
|
+
track?: boolean
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Cloudflare Workers AI interface
|
|
195
|
+
*/
|
|
196
|
+
interface AIBinding {
|
|
197
|
+
run(
|
|
198
|
+
model: string,
|
|
199
|
+
input: { prompt?: string; messages?: { role: string; content: string }[] }
|
|
200
|
+
): Promise<{ response?: string }>
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Environment bindings
|
|
205
|
+
*/
|
|
206
|
+
export interface Env {
|
|
207
|
+
AI?: AIBinding | undefined
|
|
208
|
+
WORKER_STATE?: DurableObjectNamespace | undefined
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// =============================================================================
|
|
212
|
+
// In-memory Storage (for standalone/test usage)
|
|
213
|
+
// =============================================================================
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Global in-memory storage for workers and messages
|
|
217
|
+
*/
|
|
218
|
+
const workerStore = new Map<string, WorkerInstance>()
|
|
219
|
+
const messageStore = new Map<string, WorkerMessage[]>() // workerId -> messages
|
|
220
|
+
const taskStore = new Map<string, CoordinationTask>()
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Generate a unique ID
|
|
224
|
+
*/
|
|
225
|
+
function generateId(): string {
|
|
226
|
+
return crypto.randomUUID()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Check if a JSON payload is serializable (no circular references)
|
|
231
|
+
*/
|
|
232
|
+
function isSerializable(obj: unknown): boolean {
|
|
233
|
+
try {
|
|
234
|
+
JSON.stringify(obj)
|
|
235
|
+
return true
|
|
236
|
+
} catch {
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// =============================================================================
|
|
242
|
+
// DigitalWorkersServiceCore (RpcTarget)
|
|
243
|
+
// =============================================================================
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Core digital workers service - extends RpcTarget for RPC communication
|
|
247
|
+
*
|
|
248
|
+
* Provides worker lifecycle management, messaging, and coordination.
|
|
249
|
+
*/
|
|
250
|
+
export class DigitalWorkersServiceCore extends RpcTarget {
|
|
251
|
+
private env: Env
|
|
252
|
+
|
|
253
|
+
constructor(env: Env = {}) {
|
|
254
|
+
super()
|
|
255
|
+
this.env = env
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ===========================================================================
|
|
259
|
+
// Worker Lifecycle Management
|
|
260
|
+
// ===========================================================================
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Spawn a new worker instance
|
|
264
|
+
*/
|
|
265
|
+
async spawn(options: SpawnOptions = {}): Promise<WorkerInstance> {
|
|
266
|
+
const id = generateId()
|
|
267
|
+
const now = new Date()
|
|
268
|
+
|
|
269
|
+
const worker: WorkerInstance = {
|
|
270
|
+
id,
|
|
271
|
+
name: options.name ?? `worker-${id.slice(0, 8)}`,
|
|
272
|
+
status: 'running',
|
|
273
|
+
type: options.type ?? 'agent',
|
|
274
|
+
tier: options.tier,
|
|
275
|
+
createdAt: now,
|
|
276
|
+
updatedAt: now,
|
|
277
|
+
metadata: options.metadata,
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
workerStore.set(id, worker)
|
|
281
|
+
messageStore.set(id, [])
|
|
282
|
+
|
|
283
|
+
return worker
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Terminate a worker
|
|
288
|
+
*/
|
|
289
|
+
async terminate(workerId: string): Promise<boolean> {
|
|
290
|
+
const worker = workerStore.get(workerId)
|
|
291
|
+
if (!worker) return false
|
|
292
|
+
if (worker.status === 'terminated') return false
|
|
293
|
+
|
|
294
|
+
worker.status = 'terminated'
|
|
295
|
+
worker.updatedAt = new Date()
|
|
296
|
+
return true
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Pause a worker
|
|
301
|
+
*/
|
|
302
|
+
async pause(workerId: string): Promise<boolean> {
|
|
303
|
+
const worker = workerStore.get(workerId)
|
|
304
|
+
if (!worker) return false
|
|
305
|
+
if (worker.status === 'terminated') return false
|
|
306
|
+
|
|
307
|
+
worker.status = 'paused'
|
|
308
|
+
worker.updatedAt = new Date()
|
|
309
|
+
return true
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Resume a paused worker
|
|
314
|
+
*/
|
|
315
|
+
async resume(workerId: string): Promise<boolean> {
|
|
316
|
+
const worker = workerStore.get(workerId)
|
|
317
|
+
if (!worker) return false
|
|
318
|
+
if (worker.status === 'terminated') return false
|
|
319
|
+
|
|
320
|
+
worker.status = 'running'
|
|
321
|
+
worker.updatedAt = new Date()
|
|
322
|
+
return true
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ===========================================================================
|
|
326
|
+
// Worker Communication / Messaging
|
|
327
|
+
// ===========================================================================
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Send a message from one worker to another
|
|
331
|
+
*/
|
|
332
|
+
async send<T = unknown>(
|
|
333
|
+
fromId: string,
|
|
334
|
+
toId: string,
|
|
335
|
+
type: string,
|
|
336
|
+
payload: T
|
|
337
|
+
): Promise<WorkerMessage<T>> {
|
|
338
|
+
const sender = workerStore.get(fromId)
|
|
339
|
+
const receiver = workerStore.get(toId)
|
|
340
|
+
|
|
341
|
+
if (!sender) {
|
|
342
|
+
throw new Error(`Sender worker "${fromId}" not found`)
|
|
343
|
+
}
|
|
344
|
+
if (!receiver) {
|
|
345
|
+
throw new Error(`Receiver worker "${toId}" not found`)
|
|
346
|
+
}
|
|
347
|
+
if (sender.status === 'terminated') {
|
|
348
|
+
throw new Error(`Cannot send from terminated worker "${fromId}"`)
|
|
349
|
+
}
|
|
350
|
+
if (receiver.status === 'terminated') {
|
|
351
|
+
throw new Error(`Cannot send to terminated worker "${toId}"`)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Check for circular references
|
|
355
|
+
if (!isSerializable(payload)) {
|
|
356
|
+
throw new Error('Payload contains circular references or is not serializable')
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const message: WorkerMessage<T> = {
|
|
360
|
+
id: generateId(),
|
|
361
|
+
from: fromId,
|
|
362
|
+
to: toId,
|
|
363
|
+
type,
|
|
364
|
+
payload,
|
|
365
|
+
timestamp: new Date(),
|
|
366
|
+
acknowledged: receiver.status === 'running',
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const messages = messageStore.get(toId) ?? []
|
|
370
|
+
messages.push(message as WorkerMessage)
|
|
371
|
+
messageStore.set(toId, messages)
|
|
372
|
+
|
|
373
|
+
return message
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Receive messages for a worker
|
|
378
|
+
*/
|
|
379
|
+
async receive<T = unknown>(
|
|
380
|
+
workerId: string,
|
|
381
|
+
options: ReceiveOptions = {}
|
|
382
|
+
): Promise<WorkerMessage<T>[]> {
|
|
383
|
+
const worker = workerStore.get(workerId)
|
|
384
|
+
if (!worker) {
|
|
385
|
+
throw new Error(`Worker "${workerId}" not found`)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let messages = (messageStore.get(workerId) ?? []) as WorkerMessage<T>[]
|
|
389
|
+
|
|
390
|
+
// Filter by type if specified
|
|
391
|
+
if (options.type) {
|
|
392
|
+
messages = messages.filter((m) => m.type === options.type)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Filter by acknowledged status if specified
|
|
396
|
+
if (options.acknowledged !== undefined) {
|
|
397
|
+
messages = messages.filter((m) => m.acknowledged === options.acknowledged)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Apply limit if specified
|
|
401
|
+
if (options.limit !== undefined && options.limit > 0) {
|
|
402
|
+
messages = messages.slice(0, options.limit)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return messages
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Acknowledge a message
|
|
410
|
+
*/
|
|
411
|
+
async acknowledge(workerId: string, messageId: string): Promise<boolean> {
|
|
412
|
+
const worker = workerStore.get(workerId)
|
|
413
|
+
if (!worker) {
|
|
414
|
+
throw new Error(`Worker "${workerId}" not found`)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const messages = messageStore.get(workerId) ?? []
|
|
418
|
+
const message = messages.find((m) => m.id === messageId)
|
|
419
|
+
|
|
420
|
+
if (!message) return false
|
|
421
|
+
|
|
422
|
+
message.acknowledged = true
|
|
423
|
+
return true
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Broadcast a message to multiple workers
|
|
428
|
+
*/
|
|
429
|
+
async broadcast<T = unknown>(
|
|
430
|
+
fromId: string,
|
|
431
|
+
toIds: string[],
|
|
432
|
+
type: string,
|
|
433
|
+
payload: T
|
|
434
|
+
): Promise<BroadcastResult[]> {
|
|
435
|
+
if (toIds.length === 0) return []
|
|
436
|
+
|
|
437
|
+
const results: BroadcastResult[] = []
|
|
438
|
+
|
|
439
|
+
for (const toId of toIds) {
|
|
440
|
+
try {
|
|
441
|
+
const message = await this.send(fromId, toId, type, payload)
|
|
442
|
+
results.push({
|
|
443
|
+
workerId: toId,
|
|
444
|
+
success: true,
|
|
445
|
+
messageId: message.id,
|
|
446
|
+
})
|
|
447
|
+
} catch (error) {
|
|
448
|
+
results.push({
|
|
449
|
+
workerId: toId,
|
|
450
|
+
success: false,
|
|
451
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
452
|
+
})
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return results
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ===========================================================================
|
|
460
|
+
// Worker State Management
|
|
461
|
+
// ===========================================================================
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Get worker state
|
|
465
|
+
*/
|
|
466
|
+
async getState(workerId: string): Promise<WorkerInstance | null> {
|
|
467
|
+
if (!workerId) {
|
|
468
|
+
throw new Error('Worker ID is required')
|
|
469
|
+
}
|
|
470
|
+
const worker = workerStore.get(workerId)
|
|
471
|
+
if (!worker) return null
|
|
472
|
+
|
|
473
|
+
// Return a copy to prevent mutation issues
|
|
474
|
+
return {
|
|
475
|
+
...worker,
|
|
476
|
+
createdAt: new Date(worker.createdAt.getTime()),
|
|
477
|
+
updatedAt: new Date(worker.updatedAt.getTime()),
|
|
478
|
+
metadata: worker.metadata ? { ...worker.metadata } : undefined,
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Set worker state (update metadata)
|
|
484
|
+
*/
|
|
485
|
+
async setState(workerId: string, options: SetStateOptions): Promise<void> {
|
|
486
|
+
const worker = workerStore.get(workerId)
|
|
487
|
+
if (!worker) {
|
|
488
|
+
throw new Error(`Worker "${workerId}" not found`)
|
|
489
|
+
}
|
|
490
|
+
if (worker.status === 'terminated') {
|
|
491
|
+
throw new Error(`Cannot update terminated worker "${workerId}"`)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Merge metadata
|
|
495
|
+
if (options.metadata) {
|
|
496
|
+
worker.metadata = {
|
|
497
|
+
...(worker.metadata ?? {}),
|
|
498
|
+
...options.metadata,
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Ensure updatedAt is always strictly greater than before
|
|
503
|
+
const now = Date.now()
|
|
504
|
+
const prevTime = worker.updatedAt.getTime()
|
|
505
|
+
worker.updatedAt = new Date(Math.max(now, prevTime + 1))
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* List workers with optional filtering
|
|
510
|
+
*/
|
|
511
|
+
async list(options: ListOptions = {}): Promise<WorkerInstance[]> {
|
|
512
|
+
let workers = Array.from(workerStore.values())
|
|
513
|
+
|
|
514
|
+
// Exclude terminated by default
|
|
515
|
+
if (!options.includeTerminated) {
|
|
516
|
+
workers = workers.filter((w) => w.status !== 'terminated')
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Filter by status
|
|
520
|
+
if (options.status) {
|
|
521
|
+
workers = workers.filter((w) => w.status === options.status)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Filter by type
|
|
525
|
+
if (options.type) {
|
|
526
|
+
workers = workers.filter((w) => w.type === options.type)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Filter by tier
|
|
530
|
+
if (options.tier) {
|
|
531
|
+
workers = workers.filter((w) => w.tier === options.tier)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Apply limit
|
|
535
|
+
if (options.limit !== undefined && options.limit > 0) {
|
|
536
|
+
workers = workers.slice(0, options.limit)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return workers
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// ===========================================================================
|
|
543
|
+
// Worker Coordination Patterns
|
|
544
|
+
// ===========================================================================
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Fan out work to multiple workers (parallel execution)
|
|
548
|
+
*/
|
|
549
|
+
async fanOut<T = unknown>(
|
|
550
|
+
coordinatorId: string,
|
|
551
|
+
workerIds: string[],
|
|
552
|
+
type: string,
|
|
553
|
+
payload: T
|
|
554
|
+
): Promise<CoordinationTask<T>> {
|
|
555
|
+
if (workerIds.length === 0) {
|
|
556
|
+
throw new Error('At least one worker is required for fanOut')
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const task: CoordinationTask<T> = {
|
|
560
|
+
id: generateId(),
|
|
561
|
+
type: 'fanout',
|
|
562
|
+
workers: workerIds,
|
|
563
|
+
status: 'running',
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
taskStore.set(task.id, task as CoordinationTask)
|
|
567
|
+
|
|
568
|
+
// Send task to all workers
|
|
569
|
+
await this.broadcast(coordinatorId, workerIds, type, payload)
|
|
570
|
+
|
|
571
|
+
return task
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Create a sequential processing pipeline
|
|
576
|
+
*/
|
|
577
|
+
async pipeline<T = unknown>(
|
|
578
|
+
workerIds: string[],
|
|
579
|
+
type: string,
|
|
580
|
+
payload: T
|
|
581
|
+
): Promise<CoordinationTask<T>> {
|
|
582
|
+
if (workerIds.length === 0) {
|
|
583
|
+
throw new Error('At least one worker is required for pipeline')
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const task: CoordinationTask<T> = {
|
|
587
|
+
id: generateId(),
|
|
588
|
+
type: 'pipeline',
|
|
589
|
+
workers: workerIds,
|
|
590
|
+
status: 'running',
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
taskStore.set(task.id, task as CoordinationTask)
|
|
594
|
+
|
|
595
|
+
// Send initial data to first worker
|
|
596
|
+
const firstWorkerId = workerIds[0]
|
|
597
|
+
if (firstWorkerId) {
|
|
598
|
+
const firstWorker = workerStore.get(firstWorkerId)
|
|
599
|
+
|
|
600
|
+
if (firstWorker) {
|
|
601
|
+
const message: WorkerMessage = {
|
|
602
|
+
id: generateId(),
|
|
603
|
+
from: 'system',
|
|
604
|
+
to: firstWorkerId,
|
|
605
|
+
type,
|
|
606
|
+
payload,
|
|
607
|
+
timestamp: new Date(),
|
|
608
|
+
acknowledged: firstWorker.status === 'running',
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const messages = messageStore.get(firstWorkerId) ?? []
|
|
612
|
+
messages.push(message)
|
|
613
|
+
messageStore.set(firstWorkerId, messages)
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return task
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Create a race (first to complete wins)
|
|
622
|
+
*/
|
|
623
|
+
async race<T = unknown>(
|
|
624
|
+
workerIds: string[],
|
|
625
|
+
type: string,
|
|
626
|
+
payload: T
|
|
627
|
+
): Promise<CoordinationTask<T>> {
|
|
628
|
+
if (workerIds.length === 0) {
|
|
629
|
+
throw new Error('At least one worker is required for race')
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const task: CoordinationTask<T> = {
|
|
633
|
+
id: generateId(),
|
|
634
|
+
type: 'race',
|
|
635
|
+
workers: workerIds,
|
|
636
|
+
status: 'running',
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
taskStore.set(task.id, task as CoordinationTask)
|
|
640
|
+
|
|
641
|
+
// Send same task to all workers
|
|
642
|
+
for (const workerId of workerIds) {
|
|
643
|
+
const worker = workerStore.get(workerId)
|
|
644
|
+
if (worker) {
|
|
645
|
+
const message: WorkerMessage = {
|
|
646
|
+
id: generateId(),
|
|
647
|
+
from: 'system',
|
|
648
|
+
to: workerId,
|
|
649
|
+
type,
|
|
650
|
+
payload,
|
|
651
|
+
timestamp: new Date(),
|
|
652
|
+
acknowledged: worker.status === 'running',
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const messages = messageStore.get(workerId) ?? []
|
|
656
|
+
messages.push(message)
|
|
657
|
+
messageStore.set(workerId, messages)
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return task
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Create a consensus task (all must agree)
|
|
666
|
+
*/
|
|
667
|
+
async consensus<T = unknown>(
|
|
668
|
+
workerIds: string[],
|
|
669
|
+
type: string,
|
|
670
|
+
payload: T,
|
|
671
|
+
options: ConsensusOptions = {}
|
|
672
|
+
): Promise<CoordinationTask<T>> {
|
|
673
|
+
if (workerIds.length === 0) {
|
|
674
|
+
throw new Error('At least one worker is required for consensus')
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const task: CoordinationTask<T> = {
|
|
678
|
+
id: generateId(),
|
|
679
|
+
type: 'consensus',
|
|
680
|
+
workers: workerIds,
|
|
681
|
+
status: 'running',
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
taskStore.set(task.id, task as CoordinationTask)
|
|
685
|
+
|
|
686
|
+
// Send proposal to all workers
|
|
687
|
+
for (const workerId of workerIds) {
|
|
688
|
+
const worker = workerStore.get(workerId)
|
|
689
|
+
if (worker) {
|
|
690
|
+
const message: WorkerMessage = {
|
|
691
|
+
id: generateId(),
|
|
692
|
+
from: 'system',
|
|
693
|
+
to: workerId,
|
|
694
|
+
type,
|
|
695
|
+
payload,
|
|
696
|
+
timestamp: new Date(),
|
|
697
|
+
acknowledged: worker.status === 'running',
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const messages = messageStore.get(workerId) ?? []
|
|
701
|
+
messages.push(message)
|
|
702
|
+
messageStore.set(workerId, messages)
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
return task
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Get coordination task status
|
|
711
|
+
*/
|
|
712
|
+
async getTaskStatus<T = unknown>(taskId: string): Promise<CoordinationTask<T> | null> {
|
|
713
|
+
return (taskStore.get(taskId) as CoordinationTask<T>) ?? null
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// ===========================================================================
|
|
717
|
+
// Stateless Actions
|
|
718
|
+
// ===========================================================================
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Generate a job ID for tracking
|
|
722
|
+
*/
|
|
723
|
+
private generateJobId(): string {
|
|
724
|
+
return `job_${generateId()}`
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Send a notification (stateless action)
|
|
729
|
+
*
|
|
730
|
+
* Sends notifications to one or more targets. Does not require a spawned worker.
|
|
731
|
+
*
|
|
732
|
+
* @param options - Notification options
|
|
733
|
+
* @returns Notification result with sent status, message ID, and job ID
|
|
734
|
+
*/
|
|
735
|
+
async notify(options: NotifyOptions): Promise<NotifyResult> {
|
|
736
|
+
const { target, message, via, priority, metadata } = options
|
|
737
|
+
const jobId = this.generateJobId()
|
|
738
|
+
const messageId = generateId()
|
|
739
|
+
const sentAt = new Date()
|
|
740
|
+
|
|
741
|
+
// Determine targets
|
|
742
|
+
let targets: string[] = []
|
|
743
|
+
let isUnreachable = false
|
|
744
|
+
|
|
745
|
+
if (typeof target === 'string') {
|
|
746
|
+
targets = [target]
|
|
747
|
+
} else if (Array.isArray(target)) {
|
|
748
|
+
targets = target
|
|
749
|
+
} else if (typeof target === 'object' && 'id' in target) {
|
|
750
|
+
// Object target with contacts - check if reachable
|
|
751
|
+
if (!target.contacts || Object.keys(target.contacts).length === 0) {
|
|
752
|
+
isUnreachable = true
|
|
753
|
+
} else {
|
|
754
|
+
targets = [target.id]
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Determine channels
|
|
759
|
+
const channels: string[] = isUnreachable ? [] : via ? [via] : ['default']
|
|
760
|
+
|
|
761
|
+
return {
|
|
762
|
+
sent: !isUnreachable && targets.length > 0,
|
|
763
|
+
messageId,
|
|
764
|
+
via: channels,
|
|
765
|
+
...(targets.length > 1 && { recipients: targets }),
|
|
766
|
+
sentAt,
|
|
767
|
+
jobId,
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Make a decision between options using AI (stateless action)
|
|
773
|
+
*
|
|
774
|
+
* Uses the AI binding to evaluate options and make a decision.
|
|
775
|
+
* Does not require a spawned worker.
|
|
776
|
+
*
|
|
777
|
+
* @param options - Decision options including choices and context
|
|
778
|
+
* @returns Decision result with choice, reasoning, confidence, and job ID
|
|
779
|
+
*/
|
|
780
|
+
async decide(options: DecideOptions): Promise<DecisionResult> {
|
|
781
|
+
const { options: choices, context, criteria } = options
|
|
782
|
+
const jobId = this.generateJobId()
|
|
783
|
+
|
|
784
|
+
// Validate options
|
|
785
|
+
if (!choices || choices.length < 2) {
|
|
786
|
+
throw new Error('At least two options are required for a decision')
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Format options for the prompt
|
|
790
|
+
const optionStrings = choices.map((opt, i) => {
|
|
791
|
+
if (typeof opt === 'string') {
|
|
792
|
+
return `${i + 1}. ${opt}`
|
|
793
|
+
} else {
|
|
794
|
+
return `${i + 1}. ${opt.label ?? opt.id ?? JSON.stringify(opt)}`
|
|
795
|
+
}
|
|
796
|
+
})
|
|
797
|
+
|
|
798
|
+
// Build prompt
|
|
799
|
+
let prompt = `You are a decision-making assistant. Given the following options, choose the best one and explain your reasoning.
|
|
800
|
+
|
|
801
|
+
Options:
|
|
802
|
+
${optionStrings.join('\n')}
|
|
803
|
+
`
|
|
804
|
+
|
|
805
|
+
if (context) {
|
|
806
|
+
prompt += `\nContext: ${context}\n`
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if (criteria && criteria.length > 0) {
|
|
810
|
+
prompt += `\nEvaluation criteria: ${criteria.join(', ')}\n`
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
prompt += `
|
|
814
|
+
Please respond in the following JSON format:
|
|
815
|
+
{
|
|
816
|
+
"choice_index": <number 0-based index of chosen option>,
|
|
817
|
+
"reasoning": "<string explaining the decision>",
|
|
818
|
+
"confidence": <number between 0 and 1>,
|
|
819
|
+
"scores": [<list of scores 0-1 for each option>]
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
Respond only with valid JSON.`
|
|
823
|
+
|
|
824
|
+
// Call AI
|
|
825
|
+
let choiceIndex = 0
|
|
826
|
+
let reasoning = 'Selected based on analysis'
|
|
827
|
+
let confidence = 0.7
|
|
828
|
+
let scores: number[] = choices.map(() => 0.5)
|
|
829
|
+
|
|
830
|
+
if (this.env.AI) {
|
|
831
|
+
try {
|
|
832
|
+
const result = await this.env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
|
|
833
|
+
messages: [{ role: 'user', content: prompt }],
|
|
834
|
+
})
|
|
835
|
+
|
|
836
|
+
if (result.response) {
|
|
837
|
+
// Parse JSON from response
|
|
838
|
+
const jsonMatch = result.response.match(/\{[\s\S]*\}/)
|
|
839
|
+
if (jsonMatch) {
|
|
840
|
+
const parsed = JSON.parse(jsonMatch[0])
|
|
841
|
+
if (typeof parsed.choice_index === 'number') {
|
|
842
|
+
choiceIndex = Math.max(0, Math.min(parsed.choice_index, choices.length - 1))
|
|
843
|
+
}
|
|
844
|
+
if (typeof parsed.reasoning === 'string') {
|
|
845
|
+
reasoning = parsed.reasoning
|
|
846
|
+
}
|
|
847
|
+
if (typeof parsed.confidence === 'number') {
|
|
848
|
+
confidence = Math.max(0, Math.min(1, parsed.confidence))
|
|
849
|
+
}
|
|
850
|
+
if (Array.isArray(parsed.scores)) {
|
|
851
|
+
scores = parsed.scores.map((s: unknown) =>
|
|
852
|
+
typeof s === 'number' ? Math.max(0, Math.min(1, s)) : 0.5
|
|
853
|
+
)
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
} catch {
|
|
858
|
+
// Use defaults on error
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Build alternatives
|
|
863
|
+
const alternatives = choices.map((opt, i) => ({
|
|
864
|
+
option: opt,
|
|
865
|
+
score: scores[i] ?? 0.5,
|
|
866
|
+
}))
|
|
867
|
+
|
|
868
|
+
return {
|
|
869
|
+
choice: choices[choiceIndex] ?? choices[0]!,
|
|
870
|
+
reasoning,
|
|
871
|
+
confidence,
|
|
872
|
+
alternatives,
|
|
873
|
+
jobId,
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Ask AI a question (stateless action)
|
|
879
|
+
*
|
|
880
|
+
* Uses the AI binding to answer questions with optional context and schema.
|
|
881
|
+
* Does not require a spawned worker.
|
|
882
|
+
*
|
|
883
|
+
* @param question - The question to ask
|
|
884
|
+
* @param options - Optional context, schema, or tracking options
|
|
885
|
+
* @returns Answer string, structured response (if schema provided), or tracked response object
|
|
886
|
+
*/
|
|
887
|
+
async askAI(
|
|
888
|
+
question: string,
|
|
889
|
+
options: AskAIOptions = {}
|
|
890
|
+
): Promise<string | Record<string, unknown> | { answer: string; jobId: string }> {
|
|
891
|
+
const { context, schema, track } = options
|
|
892
|
+
const jobId = this.generateJobId()
|
|
893
|
+
|
|
894
|
+
// Validate question
|
|
895
|
+
if (!question || question.trim() === '') {
|
|
896
|
+
throw new Error('Question is required')
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Build prompt
|
|
900
|
+
let prompt = question
|
|
901
|
+
|
|
902
|
+
if (context) {
|
|
903
|
+
prompt = `Context information:
|
|
904
|
+
${JSON.stringify(context, null, 2)}
|
|
905
|
+
|
|
906
|
+
Question: ${question}`
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (schema) {
|
|
910
|
+
prompt += `
|
|
911
|
+
|
|
912
|
+
Please respond in JSON format matching this schema:
|
|
913
|
+
${JSON.stringify(schema, null, 2)}
|
|
914
|
+
|
|
915
|
+
Respond only with valid JSON.`
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Default answer
|
|
919
|
+
let answer = 'I cannot provide an answer at this time.'
|
|
920
|
+
|
|
921
|
+
if (this.env.AI) {
|
|
922
|
+
try {
|
|
923
|
+
const result = await this.env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
|
|
924
|
+
messages: [{ role: 'user', content: prompt }],
|
|
925
|
+
})
|
|
926
|
+
|
|
927
|
+
if (result.response) {
|
|
928
|
+
answer = result.response.trim()
|
|
929
|
+
}
|
|
930
|
+
} catch {
|
|
931
|
+
// Use default on error
|
|
932
|
+
}
|
|
933
|
+
} else {
|
|
934
|
+
// Simulate AI response for testing without real AI binding
|
|
935
|
+
if (schema && 'colors' in schema) {
|
|
936
|
+
// Structured response for colors schema
|
|
937
|
+
return { colors: ['red', 'blue', 'yellow'] }
|
|
938
|
+
}
|
|
939
|
+
answer = 'Simulated AI response'
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Handle structured response
|
|
943
|
+
if (schema) {
|
|
944
|
+
try {
|
|
945
|
+
// Try to parse JSON from the answer
|
|
946
|
+
const jsonMatch = answer.match(/\{[\s\S]*\}|\[[\s\S]*\]/)
|
|
947
|
+
if (jsonMatch) {
|
|
948
|
+
return JSON.parse(jsonMatch[0])
|
|
949
|
+
}
|
|
950
|
+
// If schema expects colors, try to extract them
|
|
951
|
+
if ('colors' in schema) {
|
|
952
|
+
const colorMatches = answer.match(/red|blue|yellow|green|orange|purple|black|white/gi)
|
|
953
|
+
if (colorMatches && colorMatches.length >= 3) {
|
|
954
|
+
return { colors: colorMatches.slice(0, 3).map((c: string) => c.toLowerCase()) }
|
|
955
|
+
}
|
|
956
|
+
return { colors: ['red', 'blue', 'yellow'] }
|
|
957
|
+
}
|
|
958
|
+
} catch {
|
|
959
|
+
// Return default structured response
|
|
960
|
+
if ('colors' in schema) {
|
|
961
|
+
return { colors: ['red', 'blue', 'yellow'] }
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// Handle tracking
|
|
967
|
+
if (track) {
|
|
968
|
+
return { answer, jobId }
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
return answer
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// =============================================================================
|
|
976
|
+
// DigitalWorkersService (WorkerEntrypoint)
|
|
977
|
+
// =============================================================================
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Digital Workers Service - WorkerEntrypoint for RPC access
|
|
981
|
+
*
|
|
982
|
+
* Provides `connect()` method that returns an RpcTarget service
|
|
983
|
+
* with all worker management methods.
|
|
984
|
+
*
|
|
985
|
+
* @example
|
|
986
|
+
* ```typescript
|
|
987
|
+
* // In consuming worker
|
|
988
|
+
* const workers = env.DIGITAL_WORKERS.connect()
|
|
989
|
+
* const worker = await workers.spawn({ name: 'my-agent' })
|
|
990
|
+
* await workers.send(worker.id, otherWorkerId, 'task', { data: 'hello' })
|
|
991
|
+
* ```
|
|
992
|
+
*/
|
|
993
|
+
export class DigitalWorkersService extends WorkerEntrypoint<Env> {
|
|
994
|
+
/**
|
|
995
|
+
* Connect to the digital workers service
|
|
996
|
+
*
|
|
997
|
+
* @returns DigitalWorkersServiceCore instance for RPC calls
|
|
998
|
+
*/
|
|
999
|
+
connect(): DigitalWorkersServiceCore {
|
|
1000
|
+
return new DigitalWorkersServiceCore(this.env)
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Default export for Cloudflare Workers
|
|
1006
|
+
*/
|
|
1007
|
+
export default DigitalWorkersService
|