@ixo/oracle-runtime-workers 0.2.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.
Files changed (215) hide show
  1. package/README.md +110 -0
  2. package/package.json +77 -0
  3. package/src/attachments/classify.ts +114 -0
  4. package/src/attachments/content-blocks.ts +51 -0
  5. package/src/attachments/download.ts +235 -0
  6. package/src/attachments/extract.ts +241 -0
  7. package/src/attachments/index.ts +84 -0
  8. package/src/attachments/magic.ts +133 -0
  9. package/src/attachments/pipeline.ts +409 -0
  10. package/src/attachments/retention.ts +205 -0
  11. package/src/attachments/route.ts +41 -0
  12. package/src/attachments/sandbox-archive.ts +90 -0
  13. package/src/attachments/sanitize.ts +35 -0
  14. package/src/attachments/types.ts +86 -0
  15. package/src/attachments/view.ts +107 -0
  16. package/src/core/env.ts +200 -0
  17. package/src/core/index.ts +513 -0
  18. package/src/core/llm.ts +842 -0
  19. package/src/core/main-agent-types.ts +123 -0
  20. package/src/core/main-agent.ts +399 -0
  21. package/src/core/manifest.ts +365 -0
  22. package/src/core/meta-tools.ts +232 -0
  23. package/src/core/middlewares/byo-history-sanitizer.ts +165 -0
  24. package/src/core/middlewares/capability-gate.ts +76 -0
  25. package/src/core/middlewares/index.ts +18 -0
  26. package/src/core/middlewares/page-context.ts +98 -0
  27. package/src/core/middlewares/safety-guardrail.ts +117 -0
  28. package/src/core/middlewares/summarization.ts +99 -0
  29. package/src/core/middlewares/tool-repetition-guard.ts +125 -0
  30. package/src/core/middlewares/tool-validation.ts +83 -0
  31. package/src/core/middlewares/work-status.ts +57 -0
  32. package/src/core/openrouter-pricing.ts +124 -0
  33. package/src/core/plugin-loader.ts +192 -0
  34. package/src/core/plugins/index.ts +6 -0
  35. package/src/core/plugins/skills/index.ts +8 -0
  36. package/src/core/plugins/skills/skills-tools.ts +356 -0
  37. package/src/core/plugins/skills/skills.plugin.ts +113 -0
  38. package/src/core/plugins/weather/index.ts +8 -0
  39. package/src/core/plugins/weather/weather-client.ts +153 -0
  40. package/src/core/plugins/weather/weather-middleware.ts +27 -0
  41. package/src/core/plugins/weather/weather-sub-agent.ts +65 -0
  42. package/src/core/plugins/weather/weather-tools.ts +103 -0
  43. package/src/core/plugins/weather/weather.plugin.ts +227 -0
  44. package/src/core/prompt-composer.ts +469 -0
  45. package/src/core/registries.ts +642 -0
  46. package/src/core/runtime-context.ts +619 -0
  47. package/src/core/state.ts +108 -0
  48. package/src/core/sub-agent-fallback.ts +187 -0
  49. package/src/core/subagent-as-tool.ts +322 -0
  50. package/src/core/template.ts +245 -0
  51. package/src/core/utils.ts +246 -0
  52. package/src/core/wrap-plugin-tool.ts +100 -0
  53. package/src/do/ambient.ts +266 -0
  54. package/src/do/contracts.ts +562 -0
  55. package/src/do/debug-timers.ts +120 -0
  56. package/src/do/fresh-stub.ts +32 -0
  57. package/src/do/gateway-retry.ts +73 -0
  58. package/src/do/secrets-adapter.ts +32 -0
  59. package/src/do/size-alerts.ts +76 -0
  60. package/src/do/sse-stream.ts +472 -0
  61. package/src/do/transcript.ts +192 -0
  62. package/src/do/turn-body.ts +84 -0
  63. package/src/do/turn-metadata.ts +146 -0
  64. package/src/do/ucan-service.ts +339 -0
  65. package/src/do/user-oracle-do.ts +3035 -0
  66. package/src/gateway-worker.ts +82 -0
  67. package/src/index.ts +237 -0
  68. package/src/llm/byo-adapter.ts +59 -0
  69. package/src/llm/byo-catalog.ts +520 -0
  70. package/src/llm/byo-client.ts +280 -0
  71. package/src/llm/byo-routes.ts +255 -0
  72. package/src/llm/byo-service.ts +836 -0
  73. package/src/llm/chatgpt-oauth.ts +430 -0
  74. package/src/llm/provider-error.ts +283 -0
  75. package/src/matrix/gateway-do.ts +918 -0
  76. package/src/matrix/ingest.ts +209 -0
  77. package/src/matrix/replay-format.ts +34 -0
  78. package/src/matrix/reply-chain.ts +109 -0
  79. package/src/matrix/room-state-codec.ts +115 -0
  80. package/src/matrix/user-homeserver.ts +106 -0
  81. package/src/matrix/work-status.ts +294 -0
  82. package/src/memory/session-history-indexer.ts +328 -0
  83. package/src/owner-store/ixo-vfs-store.ts +570 -0
  84. package/src/owner-store/matrix-media-store.ts +136 -0
  85. package/src/owner-store/migrating-store.ts +185 -0
  86. package/src/owner-store/owner-copy-errors.ts +175 -0
  87. package/src/owner-store/retry.ts +45 -0
  88. package/src/owner-store/tus-upload.ts +333 -0
  89. package/src/owner-store/types.ts +0 -0
  90. package/src/plugin-api/define-plugin.ts +45 -0
  91. package/src/plugin-api/index.ts +56 -0
  92. package/src/plugin-api/oracle-plugin.ts +145 -0
  93. package/src/plugin-api/tool-helper.ts +82 -0
  94. package/src/plugin-api/types.ts +816 -0
  95. package/src/plugin-api/ucan-errors.ts +16 -0
  96. package/src/plugins/agui/agui-agent.ts +115 -0
  97. package/src/plugins/agui/agui.plugin.ts +148 -0
  98. package/src/plugins/agui/index.ts +2 -0
  99. package/src/plugins/attachments/attachments.plugin.ts +54 -0
  100. package/src/plugins/attachments/index.ts +6 -0
  101. package/src/plugins/attachments/view-attachment-tool.ts +91 -0
  102. package/src/plugins/base64.ts +26 -0
  103. package/src/plugins/composio/composio-tools.ts +365 -0
  104. package/src/plugins/composio/composio-ucan.ts +43 -0
  105. package/src/plugins/composio/composio.plugin.ts +214 -0
  106. package/src/plugins/composio/index.ts +15 -0
  107. package/src/plugins/domain-indexer/domain-indexer-agent.ts +57 -0
  108. package/src/plugins/domain-indexer/domain-indexer-tools.ts +253 -0
  109. package/src/plugins/domain-indexer/domain-indexer.plugin.ts +100 -0
  110. package/src/plugins/domain-indexer/index.ts +3 -0
  111. package/src/plugins/editor/blocknote-bridge.ts +120 -0
  112. package/src/plugins/editor/content-session.ts +331 -0
  113. package/src/plugins/editor/content-tools.ts +686 -0
  114. package/src/plugins/editor/document-model.ts +0 -0
  115. package/src/plugins/editor/editor-access-denied-tool.ts +58 -0
  116. package/src/plugins/editor/editor-agent.ts +34 -0
  117. package/src/plugins/editor/editor-config.ts +64 -0
  118. package/src/plugins/editor/editor-mx.ts +128 -0
  119. package/src/plugins/editor/editor.plugin.ts +143 -0
  120. package/src/plugins/editor/failures.ts +154 -0
  121. package/src/plugins/editor/index.ts +31 -0
  122. package/src/plugins/editor/jsdom-shim.ts +55 -0
  123. package/src/plugins/editor/prompts.ts +59 -0
  124. package/src/plugins/editor/prop-policy.ts +147 -0
  125. package/src/plugins/editor/provider.ts +386 -0
  126. package/src/plugins/editor/room-membership.ts +95 -0
  127. package/src/plugins/editor/standalone-editor-tool.ts +200 -0
  128. package/src/plugins/firecrawl/firecrawl-agent.ts +93 -0
  129. package/src/plugins/firecrawl/firecrawl-tools.ts +220 -0
  130. package/src/plugins/firecrawl/firecrawl.plugin.ts +105 -0
  131. package/src/plugins/firecrawl/index.ts +15 -0
  132. package/src/plugins/flows/action-metadata.ts +3505 -0
  133. package/src/plugins/flows/actions.ts +190 -0
  134. package/src/plugins/flows/edit.ts +314 -0
  135. package/src/plugins/flows/errors.ts +38 -0
  136. package/src/plugins/flows/explain.ts +43 -0
  137. package/src/plugins/flows/flow-doc.ts +111 -0
  138. package/src/plugins/flows/flows.plugin.ts +129 -0
  139. package/src/plugins/flows/forms.ts +330 -0
  140. package/src/plugins/flows/index.ts +2 -0
  141. package/src/plugins/flows/linkage.ts +147 -0
  142. package/src/plugins/flows/port-types.ts +53 -0
  143. package/src/plugins/flows/prompts.ts +65 -0
  144. package/src/plugins/flows/read.ts +392 -0
  145. package/src/plugins/flows/references.ts +66 -0
  146. package/src/plugins/flows/test-support.ts +165 -0
  147. package/src/plugins/flows/tools/authoring.ts +516 -0
  148. package/src/plugins/flows/tools/discovery.ts +119 -0
  149. package/src/plugins/flows/tools/forms.ts +142 -0
  150. package/src/plugins/flows/tools/inspect.ts +165 -0
  151. package/src/plugins/flows/tools/linkage.ts +117 -0
  152. package/src/plugins/flows/tools/settings.ts +184 -0
  153. package/src/plugins/flows/translator.ts +328 -0
  154. package/src/plugins/flows/types.ts +210 -0
  155. package/src/plugins/flows/ydoc-helpers.ts +543 -0
  156. package/src/plugins/index.ts +70 -0
  157. package/src/plugins/mcp-call-timeout.ts +38 -0
  158. package/src/plugins/mcp-tool-adapter.ts +87 -0
  159. package/src/plugins/memory/index.ts +17 -0
  160. package/src/plugins/memory/memory-tools.ts +463 -0
  161. package/src/plugins/memory/memory-ucan.ts +51 -0
  162. package/src/plugins/memory/memory.plugin.ts +122 -0
  163. package/src/plugins/memory/types.ts +147 -0
  164. package/src/plugins/portal/action-log.ts +34 -0
  165. package/src/plugins/portal/index.ts +2 -0
  166. package/src/plugins/portal/portal.plugin.ts +203 -0
  167. package/src/plugins/sandbox/index.ts +34 -0
  168. package/src/plugins/sandbox/sandbox-bridge.ts +295 -0
  169. package/src/plugins/sandbox/sandbox-mcp.ts +127 -0
  170. package/src/plugins/sandbox/sandbox-write-blob.ts +103 -0
  171. package/src/plugins/sandbox/sandbox.plugin.ts +599 -0
  172. package/src/plugins/tasks/index.ts +17 -0
  173. package/src/plugins/tasks/manifest.ts +81 -0
  174. package/src/plugins/tasks/middleware.ts +219 -0
  175. package/src/plugins/tasks/tasks-tools.ts +414 -0
  176. package/src/plugins/tasks/tasks.plugin.ts +74 -0
  177. package/src/plugins/ucan-failure.ts +133 -0
  178. package/src/plugins/user-preferences/index.ts +22 -0
  179. package/src/plugins/user-preferences/schema.ts +31 -0
  180. package/src/plugins/user-preferences/user-preferences-store.ts +172 -0
  181. package/src/plugins/user-preferences/user-preferences-tool.ts +131 -0
  182. package/src/plugins/user-preferences/user-preferences.plugin.ts +137 -0
  183. package/src/plugins/vfs/index.ts +40 -0
  184. package/src/plugins/vfs/vfs-auth.ts +56 -0
  185. package/src/plugins/vfs/vfs-client.ts +655 -0
  186. package/src/plugins/vfs/vfs-content.ts +181 -0
  187. package/src/plugins/vfs/vfs-errors.ts +163 -0
  188. package/src/plugins/vfs/vfs-sandbox-tools.ts +272 -0
  189. package/src/plugins/vfs/vfs-tools.ts +635 -0
  190. package/src/plugins/vfs/vfs.plugin.ts +201 -0
  191. package/src/realtime/frontend-call-registry.ts +163 -0
  192. package/src/realtime/realtime-endpoint.ts +619 -0
  193. package/src/realtime/session-socket-hub.ts +249 -0
  194. package/src/realtime/socket-io-codec.ts +187 -0
  195. package/src/secrets/jwe.ts +74 -0
  196. package/src/secrets/pin-cipher.ts +43 -0
  197. package/src/secrets/secrets-service.ts +319 -0
  198. package/src/shell/app.ts +564 -0
  199. package/src/shell/auth.ts +290 -0
  200. package/src/sqlite/blob-codec.ts +100 -0
  201. package/src/sqlite/blob-compactor.ts +146 -0
  202. package/src/sqlite/cache-config.ts +45 -0
  203. package/src/sqlite/database.ts +643 -0
  204. package/src/sqlite/do-vfs.ts +1485 -0
  205. package/src/sqlite/index.ts +61 -0
  206. package/src/sqlite/serialization.ts +370 -0
  207. package/src/sqlite/sessions-store.ts +311 -0
  208. package/src/sqlite/sqlite-saver.ts +919 -0
  209. package/src/sqlite/vacuum-policy.ts +77 -0
  210. package/src/sqlite/wa-sqlite-loader.ts +123 -0
  211. package/src/sqlite/wasm.d.ts +10 -0
  212. package/src/tasks/schedule.ts +174 -0
  213. package/src/tasks/scheduler.ts +753 -0
  214. package/src/tasks/spec.ts +125 -0
  215. package/src/tasks/store.ts +351 -0
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Minimal tus 1.0.0 client (creation + resume) for the IXO VFS
3
+ * (`/api/fs/upload`, see the VFS repo's RESUMABLE_UPLOADS.md).
4
+ *
5
+ * Reads `source()` part by part — never more than one `partSize` buffer in
6
+ * memory — and PATCHes each part at its offset. A failed part is retried
7
+ * with backoff after probing `HEAD` for the committed offset, so a part the
8
+ * server did take (response lost) is not sent twice and a part it did not
9
+ * take is resent from the same buffer. The VFS does not support
10
+ * `Upload-Defer-Length`, so the caller must know `totalLength` up front (the
11
+ * owner store measures it with a counting pass over the same source).
12
+ *
13
+ * Part rules enforced by the server: every non-final part must be a
14
+ * multiple of 64 KiB and at least 5 MiB; a single part covering the whole
15
+ * upload has no alignment demands. Every request carries fresh auth
16
+ * headers from `authHeaders()` (single-use UCAN invocations).
17
+ */
18
+ import { DEFAULT_RETRY_DELAYS_MS, isNetworkError } from './retry';
19
+
20
+ export const TUS_VERSION = '1.0.0';
21
+ export const TUS_PART_ALIGN_BYTES = 64 * 1024;
22
+ export const TUS_MIN_PART_BYTES = 5 * 1024 * 1024;
23
+
24
+ /** Statuses after which resending the same part can never succeed. */
25
+ const FATAL_STATUSES = new Set([400, 401, 403, 404, 410, 411, 412, 413, 415]);
26
+
27
+ export class TusUploadError extends Error {
28
+ constructor(
29
+ message: string,
30
+ readonly status: number | undefined,
31
+ readonly retryable: boolean,
32
+ ) {
33
+ super(message);
34
+ this.name = 'TusUploadError';
35
+ }
36
+ }
37
+
38
+ export interface TusUploadOptions {
39
+ /** Creation endpoint, e.g. `https://vfs/api/fs/upload`. */
40
+ endpoint: string;
41
+ path: string;
42
+ contentType: string;
43
+ totalLength: number;
44
+ partSize: number;
45
+ /** Re-openable byte source; read exactly once per upload attempt. */
46
+ source: () => ReadableStream<Uint8Array>;
47
+ /** Fresh auth headers for every request. */
48
+ authHeaders: () => Promise<Record<string, string>>;
49
+ fetchImpl: typeof fetch;
50
+ /** Per-request timeout (headers + body for parts). */
51
+ timeoutMs?: number;
52
+ retryDelaysMs?: readonly number[];
53
+ sleep?: (ms: number) => Promise<void>;
54
+ log?: (message: string) => void;
55
+ }
56
+
57
+ export interface TusUploadResult {
58
+ /** Null when the final PATCH was committed but its response was lost. */
59
+ fileId: string | null;
60
+ contentHash: string | null;
61
+ cid: string | null;
62
+ parts: number;
63
+ }
64
+
65
+ function base64(text: string): string {
66
+ const bytes = new TextEncoder().encode(text);
67
+ let binary = '';
68
+ for (const byte of bytes) binary += String.fromCharCode(byte);
69
+ return btoa(binary);
70
+ }
71
+
72
+ export function uploadMetadata(fields: Record<string, string>): string {
73
+ return Object.entries(fields)
74
+ .map(([key, value]) => `${key} ${base64(value)}`)
75
+ .join(',');
76
+ }
77
+
78
+ /** Reads fixed-size parts from a stream; the last one may be shorter. */
79
+ class PartReader {
80
+ private readonly reader: ReadableStreamDefaultReader<Uint8Array>;
81
+ private carry: Uint8Array | null = null;
82
+ private ended = false;
83
+
84
+ constructor(
85
+ stream: ReadableStream<Uint8Array>,
86
+ private readonly partSize: number,
87
+ ) {
88
+ this.reader = stream.getReader();
89
+ }
90
+
91
+ /** Next part, or null at end of stream. */
92
+ async next(): Promise<Uint8Array | null> {
93
+ if (this.ended && this.carry === null) return null;
94
+ const out = new Uint8Array(this.partSize);
95
+ let fill = 0;
96
+ while (fill < this.partSize) {
97
+ let chunk: Uint8Array;
98
+ if (this.carry !== null) {
99
+ chunk = this.carry;
100
+ this.carry = null;
101
+ } else if (this.ended) {
102
+ break;
103
+ } else {
104
+ const { done, value } = await this.reader.read();
105
+ if (done) {
106
+ this.ended = true;
107
+ break;
108
+ }
109
+ chunk = value;
110
+ }
111
+ const n = Math.min(chunk.byteLength, this.partSize - fill);
112
+ out.set(chunk.subarray(0, n), fill);
113
+ fill += n;
114
+ if (n < chunk.byteLength) this.carry = chunk.subarray(n);
115
+ }
116
+ if (fill === 0) return null;
117
+ return fill === this.partSize ? out : out.subarray(0, fill);
118
+ }
119
+
120
+ release(): void {
121
+ this.reader.releaseLock();
122
+ }
123
+ }
124
+
125
+ export async function tusUpload(
126
+ options: TusUploadOptions,
127
+ ): Promise<TusUploadResult> {
128
+ const {
129
+ totalLength,
130
+ partSize,
131
+ fetchImpl,
132
+ authHeaders,
133
+ timeoutMs = 120_000,
134
+ } = options;
135
+ const delays = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
136
+ const sleep =
137
+ options.sleep ??
138
+ ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));
139
+ const log = options.log ?? (() => undefined);
140
+ if (totalLength < 1)
141
+ throw new TusUploadError('tus: totalLength must be ≥ 1', undefined, false);
142
+ if (
143
+ totalLength > partSize &&
144
+ (partSize < TUS_MIN_PART_BYTES || partSize % TUS_PART_ALIGN_BYTES !== 0)
145
+ ) {
146
+ throw new TusUploadError(
147
+ `tus: partSize ${partSize} must be a multiple of ${TUS_PART_ALIGN_BYTES} and ≥ ${TUS_MIN_PART_BYTES}`,
148
+ undefined,
149
+ false,
150
+ );
151
+ }
152
+
153
+ const request = async (
154
+ method: string,
155
+ url: string,
156
+ headers: Record<string, string>,
157
+ body?: Uint8Array,
158
+ ): Promise<Response> => {
159
+ const controller = new AbortController();
160
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
161
+ try {
162
+ return await fetchImpl(url, {
163
+ method,
164
+ headers: {
165
+ ...(await authHeaders()),
166
+ 'Tus-Resumable': TUS_VERSION,
167
+ ...headers,
168
+ },
169
+ body,
170
+ signal: controller.signal,
171
+ });
172
+ } finally {
173
+ clearTimeout(timer);
174
+ }
175
+ };
176
+
177
+ const failure = async (
178
+ what: string,
179
+ res: Response,
180
+ ): Promise<TusUploadError> => {
181
+ const text = await res.text().catch(() => '');
182
+ return new TusUploadError(
183
+ `tus: ${what} → ${res.status} ${text.slice(0, 200)}`,
184
+ res.status,
185
+ !FATAL_STATUSES.has(res.status),
186
+ );
187
+ };
188
+
189
+ // ── creation ──────────────────────────────────────────────────────────
190
+ let sessionUrl: string | null = null;
191
+ for (let attempt = 0; sessionUrl === null; attempt++) {
192
+ try {
193
+ const res = await request('POST', options.endpoint, {
194
+ 'Upload-Length': String(totalLength),
195
+ 'Upload-Metadata': uploadMetadata({
196
+ path: options.path,
197
+ contentType: options.contentType,
198
+ }),
199
+ });
200
+ if (res.status !== 201) throw await failure('create', res);
201
+ const location = res.headers.get('location');
202
+ if (!location)
203
+ throw new TusUploadError(
204
+ 'tus: create answered without Location',
205
+ 201,
206
+ false,
207
+ );
208
+ sessionUrl = new URL(location, options.endpoint).toString();
209
+ } catch (error) {
210
+ const delay = delays[attempt];
211
+ if (delay === undefined || !isRetryable(error)) throw error;
212
+ log(`tus: create failed (${describe(error)}) — retry in ${delay} ms`);
213
+ await sleep(delay);
214
+ }
215
+ }
216
+
217
+ // ── parts ─────────────────────────────────────────────────────────────
218
+ const parts = new PartReader(options.source(), partSize);
219
+ let offset = 0;
220
+ let count = 0;
221
+ const result: TusUploadResult = {
222
+ fileId: null,
223
+ contentHash: null,
224
+ cid: null,
225
+ parts: 0,
226
+ };
227
+ const abortSession = async (): Promise<void> => {
228
+ try {
229
+ await request('DELETE', sessionUrl, {});
230
+ } catch {
231
+ // Best effort: R2's incomplete-multipart rule cleans up eventually.
232
+ }
233
+ };
234
+ try {
235
+ for (;;) {
236
+ const part = await parts.next();
237
+ if (part === null) break;
238
+ if (offset + part.byteLength > totalLength) {
239
+ throw new TusUploadError(
240
+ `tus: source produced more than the declared ${totalLength} bytes`,
241
+ undefined,
242
+ false,
243
+ );
244
+ }
245
+ count += 1;
246
+ const final = offset + part.byteLength === totalLength;
247
+ let sent = false;
248
+ for (let attempt = 0; !sent; attempt++) {
249
+ try {
250
+ const res = await request(
251
+ 'PATCH',
252
+ sessionUrl,
253
+ {
254
+ 'Upload-Offset': String(offset),
255
+ 'Content-Type': 'application/offset+octet-stream',
256
+ },
257
+ part,
258
+ );
259
+ if (!res.ok) throw await failure(`part ${count} @${offset}`, res);
260
+ if (final) {
261
+ result.fileId = res.headers.get('x-vfs-file-id');
262
+ result.contentHash = res.headers.get('x-vfs-content-hash');
263
+ result.cid = res.headers.get('x-vfs-cid');
264
+ }
265
+ sent = true;
266
+ } catch (error) {
267
+ const delay = delays[attempt];
268
+ if (delay === undefined || !isRetryable(error)) throw error;
269
+ log(
270
+ `tus: part ${count} @${offset} failed (${describe(error)}) — probing offset, retry in ${delay} ms`,
271
+ );
272
+ await sleep(delay);
273
+ const committed = await committedOffset(request, sessionUrl);
274
+ if (committed === offset + part.byteLength) {
275
+ // The server took it; only the response was lost. For the final
276
+ // part the session is gone with it — the caller resolves the
277
+ // file by path.
278
+ sent = true;
279
+ } else if (committed !== null && committed !== offset) {
280
+ throw new TusUploadError(
281
+ `tus: committed offset ${committed} does not match our ${offset}; the session cannot be resumed`,
282
+ undefined,
283
+ false,
284
+ );
285
+ }
286
+ }
287
+ }
288
+ offset += part.byteLength;
289
+ }
290
+ if (offset !== totalLength) {
291
+ throw new TusUploadError(
292
+ `tus: source produced ${offset} bytes but ${totalLength} were declared`,
293
+ undefined,
294
+ false,
295
+ );
296
+ }
297
+ } catch (error) {
298
+ await abortSession();
299
+ throw error;
300
+ } finally {
301
+ parts.release();
302
+ }
303
+ result.parts = count;
304
+ return result;
305
+ }
306
+
307
+ /** `HEAD` the session; null when the probe itself failed or the session is gone. */
308
+ async function committedOffset(
309
+ request: (
310
+ method: string,
311
+ url: string,
312
+ headers: Record<string, string>,
313
+ ) => Promise<Response>,
314
+ sessionUrl: string,
315
+ ): Promise<number | null> {
316
+ try {
317
+ const res = await request('HEAD', sessionUrl, {});
318
+ if (!res.ok) return null;
319
+ const value = res.headers.get('upload-offset');
320
+ return value === null ? null : Number(value);
321
+ } catch {
322
+ return null;
323
+ }
324
+ }
325
+
326
+ function isRetryable(error: unknown): boolean {
327
+ if (error instanceof TusUploadError) return error.retryable;
328
+ return isNetworkError(error);
329
+ }
330
+
331
+ function describe(error: unknown): string {
332
+ return error instanceof Error ? error.message : String(error);
333
+ }
Binary file
@@ -0,0 +1,45 @@
1
+ import type { OraclePlugin } from './oracle-plugin';
2
+
3
+ /**
4
+ * Input shape accepted by `defineOraclePlugin`. The three identity fields
5
+ * (`name`, `version`, `manifest`) are required; every other plugin method
6
+ * is optional.
7
+ */
8
+ export type DefineOraclePluginInput = Partial<OraclePlugin> &
9
+ Pick<OraclePlugin, 'name' | 'version' | 'manifest'>;
10
+
11
+ /**
12
+ * Identity-style helper for authoring a plugin as a plain object.
13
+ *
14
+ * Returns the same object the caller passed in, narrowed to `OraclePlugin`
15
+ * so type errors surface at the authoring site. The runtime treats the
16
+ * resulting POJO interchangeably with a class instance extending
17
+ * `OraclePlugin`.
18
+ *
19
+ * @throws TypeError when `name`, `version`, or `manifest` is missing.
20
+ */
21
+ export function defineOraclePlugin(
22
+ spec: DefineOraclePluginInput,
23
+ ): OraclePlugin {
24
+ if (!spec || typeof spec !== 'object') {
25
+ throw new TypeError(
26
+ 'defineOraclePlugin(spec): `spec` must be a plugin definition object.',
27
+ );
28
+ }
29
+ if (!spec.name || typeof spec.name !== 'string') {
30
+ throw new TypeError(
31
+ 'defineOraclePlugin(spec): `name` is required and must be a non-empty string.',
32
+ );
33
+ }
34
+ if (!spec.version || typeof spec.version !== 'string') {
35
+ throw new TypeError(
36
+ 'defineOraclePlugin(spec): `version` is required and must be a non-empty string.',
37
+ );
38
+ }
39
+ if (!spec.manifest || typeof spec.manifest !== 'object') {
40
+ throw new TypeError(
41
+ 'defineOraclePlugin(spec): `manifest` is required and must be an object.',
42
+ );
43
+ }
44
+ return spec;
45
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Plugin authoring surface. A plugin written against `@ixo/oracle-runtime`
3
+ * that uses tools / sub-agents / middlewares / manifest / configSchema /
4
+ * autoDetect / getSharedState compiles unchanged against this module; the
5
+ * only Node-specific hook (`getNestModules`) is replaced by `getRoutes`.
6
+ */
7
+ export { OraclePlugin } from './oracle-plugin';
8
+ export type {
9
+ PluginEnv,
10
+ PluginRoute,
11
+ PluginRouteMethod,
12
+ } from './oracle-plugin';
13
+ export { defineOraclePlugin } from './define-plugin';
14
+ export type { DefineOraclePluginInput } from './define-plugin';
15
+ export { tool } from './tool-helper';
16
+ export type { ToolHelperOptions } from './tool-helper';
17
+ export { UcanMintUnavailableError } from './ucan-errors';
18
+ export type {
19
+ ActionCallEventPayload,
20
+ AgentMiddleware,
21
+ AuthExcludedRoute,
22
+ BrowserToolCallEventPayload,
23
+ ChatOpenAIFields,
24
+ CommerceContext,
25
+ CommerceEngagement,
26
+ CommerceEngagementStatus,
27
+ CommerceGateFailure,
28
+ CommerceGateFailureReason,
29
+ CommerceInProgressEngagement,
30
+ CommerceMode,
31
+ CommercePaymentOutcome,
32
+ Logger,
33
+ ManifestExample,
34
+ MatrixEvent,
35
+ MergedConfig,
36
+ MessageCacheInvalidationPayload,
37
+ ModelRole,
38
+ OracleConfig,
39
+ OracleIdentity,
40
+ OraclePromptConfig,
41
+ PluginContext,
42
+ PluginManifest,
43
+ PluginSubAgent,
44
+ PluginTool,
45
+ ReadonlyState,
46
+ ReasoningEventPayload,
47
+ RenderComponentEventPayload,
48
+ RoomStateSnapshot,
49
+ RouterEventPayload,
50
+ RuntimeContext,
51
+ SecretIndex,
52
+ SharedAccessors,
53
+ ToolCallEventPayload,
54
+ UcanDelegation,
55
+ UserContextData,
56
+ } from './types';
@@ -0,0 +1,145 @@
1
+ import type { z } from 'zod';
2
+ import type { PluginRoute } from '../shell/app';
3
+ import type {
4
+ AgentMiddleware,
5
+ AuthExcludedRoute,
6
+ PluginContext,
7
+ PluginManifest,
8
+ PluginSubAgent,
9
+ PluginTool,
10
+ RuntimeContext,
11
+ } from './types';
12
+
13
+ /**
14
+ * The environment the plugin loader hands to `autoDetect`. On Workers this
15
+ * is the `env` bindings object (secrets, vars, and — for keys a plugin never
16
+ * reads — Durable Object namespaces), never `process.env`. Values are typed
17
+ * `unknown` because bindings are not all strings; a plugin checking for a
18
+ * secret should test `typeof env.MY_KEY === 'string' && env.MY_KEY.length > 0`.
19
+ */
20
+ export type PluginEnv = Record<string, unknown>;
21
+
22
+ /**
23
+ * A route contributed by a plugin — `{ method, path, handler(request, env) }`,
24
+ * the exact shape the Hono shell mounts (`src/shell/app.ts` owns the type).
25
+ * `path` is mounted as given (leading slash optional) with the shell's router
26
+ * syntax, so `:param` segments work. Routes are auth-locked unless the same
27
+ * plugin lists them in `getAuthExcludedRoutes()`.
28
+ */
29
+ export type { PluginRoute };
30
+
31
+ /** HTTP methods a plugin route can be mounted on. */
32
+ export type PluginRouteMethod = PluginRoute['method'];
33
+
34
+ /**
35
+ * Base class every plugin implements. Identical to the Node runtime's contract
36
+ * except for two Workers-driven changes:
37
+ *
38
+ * - `autoDetect` receives the Worker `env` bindings object (`PluginEnv`)
39
+ * rather than `NodeJS.ProcessEnv`. A Node plugin whose predicate only reads
40
+ * string keys compiles unchanged (method parameters are bivariant).
41
+ * - `getNestModules` is replaced by `getRoutes`, which returns plain
42
+ * `{ method, path, handler }` records the Hono shell mounts. There is no
43
+ * dependency-injection container on Workers; plugins that need long-lived
44
+ * state hold it on the instance (the instance lives as long as the isolate).
45
+ */
46
+ export abstract class OraclePlugin {
47
+ /** Unique plugin identifier (kebab-case by convention). */
48
+ abstract readonly name: string;
49
+
50
+ /** Plugin version. */
51
+ abstract readonly version: string;
52
+
53
+ /** The agent's structured interface to this plugin. */
54
+ abstract readonly manifest: PluginManifest;
55
+
56
+ /** Hard dependencies — boot fails if any is missing. */
57
+ readonly dependsOn?: string[];
58
+
59
+ /** Soft dependencies — plugin loads either way; branches on availability. */
60
+ readonly softDependsOn?: string[];
61
+
62
+ /** Plugin-owned env vars. Merged into the runtime's Zod schema at boot. */
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ readonly configSchema?: z.ZodObject<any>;
65
+
66
+ /**
67
+ * Predicate the loader runs against the Worker `env` bindings when the
68
+ * plugin is left at `'auto'` (or has no explicit toggle). Returning `false`
69
+ * skips the plugin. Plugins without an `autoDetect` are on-by-default.
70
+ */
71
+ autoDetect?(env: PluginEnv): boolean;
72
+
73
+ /**
74
+ * Human-readable description of what `autoDetect` checks (e.g. the env
75
+ * var name). Surfaced in boot-error messages so operators see exactly
76
+ * why the plugin was skipped.
77
+ */
78
+ readonly autoDetectHint?: string;
79
+
80
+ /** Tools the main agent can call. Called once per isolate (boot-cached). */
81
+ getTools?(ctx: PluginContext): PluginTool[] | Promise<PluginTool[]>;
82
+
83
+ /** Sub-agents the runtime auto-wraps as tools. */
84
+ getSubAgents?(ctx: PluginContext): PluginSubAgent[];
85
+
86
+ /**
87
+ * Request-time tool contributions. Called once per `createMainAgent`
88
+ * invocation with the full per-request `RuntimeContext`, so the plugin
89
+ * can decide which tools to expose based on live state (e.g. AG-UI
90
+ * actions in `state.agActions`).
91
+ *
92
+ * Results are merged with `getTools(ctx)` — both can fire on the same
93
+ * build. Plugins should pick whichever hook fits: boot-time when only
94
+ * config/identity matters; request-time when state/user/session matters.
95
+ */
96
+ getRequestTools?(rtCtx: RuntimeContext): PluginTool[] | Promise<PluginTool[]>;
97
+
98
+ /**
99
+ * Request-time sub-agent contributions. Same merge semantics as
100
+ * `getRequestTools` — boot-time `getSubAgents` and request-time
101
+ * `getRequestSubAgents` outputs are both collected.
102
+ */
103
+ getRequestSubAgents?(
104
+ rtCtx: RuntimeContext,
105
+ ): PluginSubAgent[] | Promise<PluginSubAgent[]>;
106
+
107
+ /**
108
+ * LangChain middlewares inserted after the runtime's always-on middlewares
109
+ * (summarization, capability gate, tool validation, repetition guard, retry).
110
+ */
111
+ getMiddlewares?(ctx: PluginContext): AgentMiddleware[];
112
+
113
+ /**
114
+ * Read-only accessors this plugin exposes to other plugins via `ctx.shared`.
115
+ * Pattern: this plugin computes/owns some derived value from state; others
116
+ * read it.
117
+ */
118
+ getSharedState?(): Record<
119
+ string,
120
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
121
+ (state: any, runCtx: RuntimeContext) => unknown
122
+ >;
123
+
124
+ /**
125
+ * HTTP routes the plugin contributes. The Hono shell mounts every returned
126
+ * route under the Worker's root; handlers receive the raw `Request` and the
127
+ * Worker `env`. Use for webhooks, public probes, or plugin-owned REST
128
+ * endpoints. Replaces the Node runtime's `getNestModules`.
129
+ *
130
+ * `ctx` carries the validated merged config + identity + logger so handlers
131
+ * can close over config without reading `env` themselves.
132
+ */
133
+ getRoutes?(ctx: PluginContext): PluginRoute[];
134
+
135
+ /**
136
+ * Routes owned by this plugin's `getRoutes()` that MUST NOT pass through
137
+ * the shell's UCAN auth middleware. Use for webhooks, OAuth callbacks,
138
+ * public probes — anything that doesn't authenticate via UCAN. Returning an
139
+ * empty array (or omitting the method) keeps every plugin route auth-locked.
140
+ *
141
+ * The returned `path` is matched against the request URL with the leading
142
+ * slash optional and should be the full mounted path (e.g. `weather/now`).
143
+ */
144
+ getAuthExcludedRoutes?(): AuthExcludedRoute[];
145
+ }
@@ -0,0 +1,82 @@
1
+ import type { z } from 'zod';
2
+ import type { PluginTool, RuntimeContext } from './types';
3
+
4
+ /**
5
+ * Authoring options for `tool()`. Mirrors the public surface of `PluginTool`
6
+ * minus the handler (which is the first positional argument).
7
+ */
8
+ export interface ToolHelperOptions {
9
+ /** Tool name shown to the agent. Must be unique within the plugin. */
10
+ name: string;
11
+ /** Tool description shown to the agent. */
12
+ description: string;
13
+ /** Zod schema validating the tool's input arguments. */
14
+ schema: z.ZodType;
15
+ /**
16
+ * Override visibility — by default the tool inherits the plugin's
17
+ * `manifest.visibility` setting.
18
+ */
19
+ visibility?: PluginTool['visibility'];
20
+ /**
21
+ * Billing gate marker, kept for source compatibility with the Node
22
+ * runtime's plugin contract. The Workers runtime has no commerce lane, so
23
+ * the marker is carried on the `PluginTool` but never gates binding.
24
+ */
25
+ billing?: PluginTool['billing'];
26
+ }
27
+
28
+ /**
29
+ * User-facing helper to author a plugin tool.
30
+ *
31
+ * The handler receives the validated arguments and a `RuntimeContext` built
32
+ * fresh per invocation. The runtime is responsible for bridging LangChain's
33
+ * `(args, runConfig)` calling convention into `(args, ctx)` when it wires
34
+ * the tool into the agent.
35
+ *
36
+ * @param handler - Async function invoked when the agent calls the tool.
37
+ * @param options - Tool descriptor (name, description, schema, optional visibility).
38
+ * @returns A `PluginTool` consumable by the runtime's tool registration step.
39
+ */
40
+ export function tool(
41
+ handler: (args: unknown, ctx: RuntimeContext) => Promise<unknown>,
42
+ options: ToolHelperOptions,
43
+ ): PluginTool {
44
+ if (typeof handler !== 'function') {
45
+ throw new TypeError(
46
+ 'tool(handler, options): `handler` must be a function.',
47
+ );
48
+ }
49
+ if (!options || typeof options !== 'object') {
50
+ throw new TypeError('tool(handler, options): `options` is required.');
51
+ }
52
+ const { name, description, schema, visibility, billing } = options;
53
+ if (!name || typeof name !== 'string') {
54
+ throw new TypeError(
55
+ 'tool(handler, options): `options.name` must be a non-empty string.',
56
+ );
57
+ }
58
+ if (!description || typeof description !== 'string') {
59
+ throw new TypeError(
60
+ 'tool(handler, options): `options.description` must be a non-empty string.',
61
+ );
62
+ }
63
+ if (!schema) {
64
+ throw new TypeError(
65
+ 'tool(handler, options): `options.schema` (Zod schema) is required.',
66
+ );
67
+ }
68
+
69
+ const pluginTool: PluginTool = {
70
+ name,
71
+ description,
72
+ schema,
73
+ handler,
74
+ };
75
+ if (visibility !== undefined) {
76
+ pluginTool.visibility = visibility;
77
+ }
78
+ if (billing !== undefined) {
79
+ pluginTool.billing = billing;
80
+ }
81
+ return pluginTool;
82
+ }