@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.
- package/README.md +110 -0
- package/package.json +77 -0
- package/src/attachments/classify.ts +114 -0
- package/src/attachments/content-blocks.ts +51 -0
- package/src/attachments/download.ts +235 -0
- package/src/attachments/extract.ts +241 -0
- package/src/attachments/index.ts +84 -0
- package/src/attachments/magic.ts +133 -0
- package/src/attachments/pipeline.ts +409 -0
- package/src/attachments/retention.ts +205 -0
- package/src/attachments/route.ts +41 -0
- package/src/attachments/sandbox-archive.ts +90 -0
- package/src/attachments/sanitize.ts +35 -0
- package/src/attachments/types.ts +86 -0
- package/src/attachments/view.ts +107 -0
- package/src/core/env.ts +200 -0
- package/src/core/index.ts +513 -0
- package/src/core/llm.ts +842 -0
- package/src/core/main-agent-types.ts +123 -0
- package/src/core/main-agent.ts +399 -0
- package/src/core/manifest.ts +365 -0
- package/src/core/meta-tools.ts +232 -0
- package/src/core/middlewares/byo-history-sanitizer.ts +165 -0
- package/src/core/middlewares/capability-gate.ts +76 -0
- package/src/core/middlewares/index.ts +18 -0
- package/src/core/middlewares/page-context.ts +98 -0
- package/src/core/middlewares/safety-guardrail.ts +117 -0
- package/src/core/middlewares/summarization.ts +99 -0
- package/src/core/middlewares/tool-repetition-guard.ts +125 -0
- package/src/core/middlewares/tool-validation.ts +83 -0
- package/src/core/middlewares/work-status.ts +57 -0
- package/src/core/openrouter-pricing.ts +124 -0
- package/src/core/plugin-loader.ts +192 -0
- package/src/core/plugins/index.ts +6 -0
- package/src/core/plugins/skills/index.ts +8 -0
- package/src/core/plugins/skills/skills-tools.ts +356 -0
- package/src/core/plugins/skills/skills.plugin.ts +113 -0
- package/src/core/plugins/weather/index.ts +8 -0
- package/src/core/plugins/weather/weather-client.ts +153 -0
- package/src/core/plugins/weather/weather-middleware.ts +27 -0
- package/src/core/plugins/weather/weather-sub-agent.ts +65 -0
- package/src/core/plugins/weather/weather-tools.ts +103 -0
- package/src/core/plugins/weather/weather.plugin.ts +227 -0
- package/src/core/prompt-composer.ts +469 -0
- package/src/core/registries.ts +642 -0
- package/src/core/runtime-context.ts +619 -0
- package/src/core/state.ts +108 -0
- package/src/core/sub-agent-fallback.ts +187 -0
- package/src/core/subagent-as-tool.ts +322 -0
- package/src/core/template.ts +245 -0
- package/src/core/utils.ts +246 -0
- package/src/core/wrap-plugin-tool.ts +100 -0
- package/src/do/ambient.ts +266 -0
- package/src/do/contracts.ts +562 -0
- package/src/do/debug-timers.ts +120 -0
- package/src/do/fresh-stub.ts +32 -0
- package/src/do/gateway-retry.ts +73 -0
- package/src/do/secrets-adapter.ts +32 -0
- package/src/do/size-alerts.ts +76 -0
- package/src/do/sse-stream.ts +472 -0
- package/src/do/transcript.ts +192 -0
- package/src/do/turn-body.ts +84 -0
- package/src/do/turn-metadata.ts +146 -0
- package/src/do/ucan-service.ts +339 -0
- package/src/do/user-oracle-do.ts +3035 -0
- package/src/gateway-worker.ts +82 -0
- package/src/index.ts +237 -0
- package/src/llm/byo-adapter.ts +59 -0
- package/src/llm/byo-catalog.ts +520 -0
- package/src/llm/byo-client.ts +280 -0
- package/src/llm/byo-routes.ts +255 -0
- package/src/llm/byo-service.ts +836 -0
- package/src/llm/chatgpt-oauth.ts +430 -0
- package/src/llm/provider-error.ts +283 -0
- package/src/matrix/gateway-do.ts +918 -0
- package/src/matrix/ingest.ts +209 -0
- package/src/matrix/replay-format.ts +34 -0
- package/src/matrix/reply-chain.ts +109 -0
- package/src/matrix/room-state-codec.ts +115 -0
- package/src/matrix/user-homeserver.ts +106 -0
- package/src/matrix/work-status.ts +294 -0
- package/src/memory/session-history-indexer.ts +328 -0
- package/src/owner-store/ixo-vfs-store.ts +570 -0
- package/src/owner-store/matrix-media-store.ts +136 -0
- package/src/owner-store/migrating-store.ts +185 -0
- package/src/owner-store/owner-copy-errors.ts +175 -0
- package/src/owner-store/retry.ts +45 -0
- package/src/owner-store/tus-upload.ts +333 -0
- package/src/owner-store/types.ts +0 -0
- package/src/plugin-api/define-plugin.ts +45 -0
- package/src/plugin-api/index.ts +56 -0
- package/src/plugin-api/oracle-plugin.ts +145 -0
- package/src/plugin-api/tool-helper.ts +82 -0
- package/src/plugin-api/types.ts +816 -0
- package/src/plugin-api/ucan-errors.ts +16 -0
- package/src/plugins/agui/agui-agent.ts +115 -0
- package/src/plugins/agui/agui.plugin.ts +148 -0
- package/src/plugins/agui/index.ts +2 -0
- package/src/plugins/attachments/attachments.plugin.ts +54 -0
- package/src/plugins/attachments/index.ts +6 -0
- package/src/plugins/attachments/view-attachment-tool.ts +91 -0
- package/src/plugins/base64.ts +26 -0
- package/src/plugins/composio/composio-tools.ts +365 -0
- package/src/plugins/composio/composio-ucan.ts +43 -0
- package/src/plugins/composio/composio.plugin.ts +214 -0
- package/src/plugins/composio/index.ts +15 -0
- package/src/plugins/domain-indexer/domain-indexer-agent.ts +57 -0
- package/src/plugins/domain-indexer/domain-indexer-tools.ts +253 -0
- package/src/plugins/domain-indexer/domain-indexer.plugin.ts +100 -0
- package/src/plugins/domain-indexer/index.ts +3 -0
- package/src/plugins/editor/blocknote-bridge.ts +120 -0
- package/src/plugins/editor/content-session.ts +331 -0
- package/src/plugins/editor/content-tools.ts +686 -0
- package/src/plugins/editor/document-model.ts +0 -0
- package/src/plugins/editor/editor-access-denied-tool.ts +58 -0
- package/src/plugins/editor/editor-agent.ts +34 -0
- package/src/plugins/editor/editor-config.ts +64 -0
- package/src/plugins/editor/editor-mx.ts +128 -0
- package/src/plugins/editor/editor.plugin.ts +143 -0
- package/src/plugins/editor/failures.ts +154 -0
- package/src/plugins/editor/index.ts +31 -0
- package/src/plugins/editor/jsdom-shim.ts +55 -0
- package/src/plugins/editor/prompts.ts +59 -0
- package/src/plugins/editor/prop-policy.ts +147 -0
- package/src/plugins/editor/provider.ts +386 -0
- package/src/plugins/editor/room-membership.ts +95 -0
- package/src/plugins/editor/standalone-editor-tool.ts +200 -0
- package/src/plugins/firecrawl/firecrawl-agent.ts +93 -0
- package/src/plugins/firecrawl/firecrawl-tools.ts +220 -0
- package/src/plugins/firecrawl/firecrawl.plugin.ts +105 -0
- package/src/plugins/firecrawl/index.ts +15 -0
- package/src/plugins/flows/action-metadata.ts +3505 -0
- package/src/plugins/flows/actions.ts +190 -0
- package/src/plugins/flows/edit.ts +314 -0
- package/src/plugins/flows/errors.ts +38 -0
- package/src/plugins/flows/explain.ts +43 -0
- package/src/plugins/flows/flow-doc.ts +111 -0
- package/src/plugins/flows/flows.plugin.ts +129 -0
- package/src/plugins/flows/forms.ts +330 -0
- package/src/plugins/flows/index.ts +2 -0
- package/src/plugins/flows/linkage.ts +147 -0
- package/src/plugins/flows/port-types.ts +53 -0
- package/src/plugins/flows/prompts.ts +65 -0
- package/src/plugins/flows/read.ts +392 -0
- package/src/plugins/flows/references.ts +66 -0
- package/src/plugins/flows/test-support.ts +165 -0
- package/src/plugins/flows/tools/authoring.ts +516 -0
- package/src/plugins/flows/tools/discovery.ts +119 -0
- package/src/plugins/flows/tools/forms.ts +142 -0
- package/src/plugins/flows/tools/inspect.ts +165 -0
- package/src/plugins/flows/tools/linkage.ts +117 -0
- package/src/plugins/flows/tools/settings.ts +184 -0
- package/src/plugins/flows/translator.ts +328 -0
- package/src/plugins/flows/types.ts +210 -0
- package/src/plugins/flows/ydoc-helpers.ts +543 -0
- package/src/plugins/index.ts +70 -0
- package/src/plugins/mcp-call-timeout.ts +38 -0
- package/src/plugins/mcp-tool-adapter.ts +87 -0
- package/src/plugins/memory/index.ts +17 -0
- package/src/plugins/memory/memory-tools.ts +463 -0
- package/src/plugins/memory/memory-ucan.ts +51 -0
- package/src/plugins/memory/memory.plugin.ts +122 -0
- package/src/plugins/memory/types.ts +147 -0
- package/src/plugins/portal/action-log.ts +34 -0
- package/src/plugins/portal/index.ts +2 -0
- package/src/plugins/portal/portal.plugin.ts +203 -0
- package/src/plugins/sandbox/index.ts +34 -0
- package/src/plugins/sandbox/sandbox-bridge.ts +295 -0
- package/src/plugins/sandbox/sandbox-mcp.ts +127 -0
- package/src/plugins/sandbox/sandbox-write-blob.ts +103 -0
- package/src/plugins/sandbox/sandbox.plugin.ts +599 -0
- package/src/plugins/tasks/index.ts +17 -0
- package/src/plugins/tasks/manifest.ts +81 -0
- package/src/plugins/tasks/middleware.ts +219 -0
- package/src/plugins/tasks/tasks-tools.ts +414 -0
- package/src/plugins/tasks/tasks.plugin.ts +74 -0
- package/src/plugins/ucan-failure.ts +133 -0
- package/src/plugins/user-preferences/index.ts +22 -0
- package/src/plugins/user-preferences/schema.ts +31 -0
- package/src/plugins/user-preferences/user-preferences-store.ts +172 -0
- package/src/plugins/user-preferences/user-preferences-tool.ts +131 -0
- package/src/plugins/user-preferences/user-preferences.plugin.ts +137 -0
- package/src/plugins/vfs/index.ts +40 -0
- package/src/plugins/vfs/vfs-auth.ts +56 -0
- package/src/plugins/vfs/vfs-client.ts +655 -0
- package/src/plugins/vfs/vfs-content.ts +181 -0
- package/src/plugins/vfs/vfs-errors.ts +163 -0
- package/src/plugins/vfs/vfs-sandbox-tools.ts +272 -0
- package/src/plugins/vfs/vfs-tools.ts +635 -0
- package/src/plugins/vfs/vfs.plugin.ts +201 -0
- package/src/realtime/frontend-call-registry.ts +163 -0
- package/src/realtime/realtime-endpoint.ts +619 -0
- package/src/realtime/session-socket-hub.ts +249 -0
- package/src/realtime/socket-io-codec.ts +187 -0
- package/src/secrets/jwe.ts +74 -0
- package/src/secrets/pin-cipher.ts +43 -0
- package/src/secrets/secrets-service.ts +319 -0
- package/src/shell/app.ts +564 -0
- package/src/shell/auth.ts +290 -0
- package/src/sqlite/blob-codec.ts +100 -0
- package/src/sqlite/blob-compactor.ts +146 -0
- package/src/sqlite/cache-config.ts +45 -0
- package/src/sqlite/database.ts +643 -0
- package/src/sqlite/do-vfs.ts +1485 -0
- package/src/sqlite/index.ts +61 -0
- package/src/sqlite/serialization.ts +370 -0
- package/src/sqlite/sessions-store.ts +311 -0
- package/src/sqlite/sqlite-saver.ts +919 -0
- package/src/sqlite/vacuum-policy.ts +77 -0
- package/src/sqlite/wa-sqlite-loader.ts +123 -0
- package/src/sqlite/wasm.d.ts +10 -0
- package/src/tasks/schedule.ts +174 -0
- package/src/tasks/scheduler.ts +753 -0
- package/src/tasks/spec.ts +125 -0
- package/src/tasks/store.ts +351 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript → client DTO. Port of `@ixo/common`'s
|
|
3
|
+
* `transformGraphStateMessageToListMessageResponse` so `GET /messages/:id`
|
|
4
|
+
* returns the exact shape the Portal / client SDK already consume.
|
|
5
|
+
*/
|
|
6
|
+
import type {
|
|
7
|
+
AIMessage,
|
|
8
|
+
BaseMessage,
|
|
9
|
+
ToolMessage,
|
|
10
|
+
} from '@langchain/core/messages';
|
|
11
|
+
import {
|
|
12
|
+
isAttachmentPlaceholderText,
|
|
13
|
+
isAttachmentViewMessage,
|
|
14
|
+
} from '../attachments/retention';
|
|
15
|
+
|
|
16
|
+
export interface ToolCallDto {
|
|
17
|
+
name: string;
|
|
18
|
+
id: string;
|
|
19
|
+
args: unknown;
|
|
20
|
+
status?: 'isRunning' | 'done';
|
|
21
|
+
output?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AttachmentMeta {
|
|
25
|
+
filename: string;
|
|
26
|
+
mimetype: string;
|
|
27
|
+
size?: number;
|
|
28
|
+
mxcUri?: string;
|
|
29
|
+
eventId?: string;
|
|
30
|
+
category?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface MessageDto {
|
|
34
|
+
id: string;
|
|
35
|
+
type: 'ai' | 'human';
|
|
36
|
+
content: string;
|
|
37
|
+
toolCalls?: ToolCallDto[];
|
|
38
|
+
reasoning?: string;
|
|
39
|
+
isComplete?: boolean;
|
|
40
|
+
isReasoning?: boolean;
|
|
41
|
+
attachment?: AttachmentMeta;
|
|
42
|
+
attachments?: AttachmentMeta[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ListMessagesResponse {
|
|
46
|
+
messages: MessageDto[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const UUID_RE =
|
|
50
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
51
|
+
|
|
52
|
+
/** Stable id for a message without one — sha256 hex of its text (as the Node runtime does). */
|
|
53
|
+
export async function uuidFromString(str: string): Promise<string> {
|
|
54
|
+
if (UUID_RE.test(str)) return str;
|
|
55
|
+
const digest = await crypto.subtle.digest(
|
|
56
|
+
'SHA-256',
|
|
57
|
+
new TextEncoder().encode(str),
|
|
58
|
+
);
|
|
59
|
+
return Array.from(new Uint8Array(digest))
|
|
60
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
61
|
+
.join('');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function reasoningToText(reasoning: unknown): string | undefined {
|
|
65
|
+
if (typeof reasoning === 'string') return reasoning || undefined;
|
|
66
|
+
if (
|
|
67
|
+
reasoning &&
|
|
68
|
+
typeof reasoning === 'object' &&
|
|
69
|
+
Array.isArray((reasoning as { summary?: unknown }).summary)
|
|
70
|
+
) {
|
|
71
|
+
const text = (reasoning as { summary: unknown[] }).summary
|
|
72
|
+
.map((part) =>
|
|
73
|
+
part &&
|
|
74
|
+
typeof part === 'object' &&
|
|
75
|
+
typeof (part as { text?: unknown }).text === 'string'
|
|
76
|
+
? (part as { text: string }).text
|
|
77
|
+
: '',
|
|
78
|
+
)
|
|
79
|
+
.join('');
|
|
80
|
+
return text || undefined;
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function contentToText(content: BaseMessage['content']): string {
|
|
86
|
+
if (typeof content === 'string') return content;
|
|
87
|
+
if (Array.isArray(content)) {
|
|
88
|
+
return content
|
|
89
|
+
.map((part) => {
|
|
90
|
+
if (typeof part === 'string') return part;
|
|
91
|
+
if (part && typeof part === 'object' && 'text' in part) {
|
|
92
|
+
const text = (part as { text?: unknown }).text;
|
|
93
|
+
// The retention placeholder is model-facing only; the transcript
|
|
94
|
+
// keeps listing the attachment from its metadata.
|
|
95
|
+
return typeof text === 'string' && !isAttachmentPlaceholderText(text)
|
|
96
|
+
? text
|
|
97
|
+
: '';
|
|
98
|
+
}
|
|
99
|
+
return '';
|
|
100
|
+
})
|
|
101
|
+
.join('');
|
|
102
|
+
}
|
|
103
|
+
return String(content);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The summarization middleware's bookkeeping message — never shown to users. */
|
|
107
|
+
export function isSummarizationMessage(message: BaseMessage): boolean {
|
|
108
|
+
const kw = message.additional_kwargs as Record<string, unknown> | undefined;
|
|
109
|
+
if (kw?.lc_summary_message === true || kw?.summary === true) return true;
|
|
110
|
+
const text = contentToText(message.content);
|
|
111
|
+
return message.type === 'system' && /summary of the conversation/i.test(text);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function transformTranscript(
|
|
115
|
+
messages: BaseMessage[],
|
|
116
|
+
): Promise<ListMessagesResponse> {
|
|
117
|
+
const acc: MessageDto[] = [];
|
|
118
|
+
for (const message of messages) {
|
|
119
|
+
const toolMsg = message.type === 'tool' ? (message as ToolMessage) : null;
|
|
120
|
+
const kw = (message.additional_kwargs ?? {}) as Record<string, unknown>;
|
|
121
|
+
const isExtractionContext = message.type === 'ai' && Boolean(kw.attachment);
|
|
122
|
+
|
|
123
|
+
if (
|
|
124
|
+
message.type !== 'system' &&
|
|
125
|
+
message.type !== 'tool' &&
|
|
126
|
+
!kw.isError &&
|
|
127
|
+
!isExtractionContext &&
|
|
128
|
+
// `view_attachment` re-attaching an offloaded payload — model-facing.
|
|
129
|
+
!isAttachmentViewMessage(message)
|
|
130
|
+
) {
|
|
131
|
+
const reasoning = reasoningToText(kw.reasoning);
|
|
132
|
+
const attachments =
|
|
133
|
+
message.type === 'human'
|
|
134
|
+
? ((kw.attachments as AttachmentMeta[] | undefined) ??
|
|
135
|
+
(kw.attachment ? [kw.attachment as AttachmentMeta] : undefined))
|
|
136
|
+
: undefined;
|
|
137
|
+
const attachment = attachments?.[0];
|
|
138
|
+
const textContent = contentToText(message.content);
|
|
139
|
+
const toolCalls = (message as AIMessage).tool_calls;
|
|
140
|
+
const dto: MessageDto = {
|
|
141
|
+
type: message.type === 'ai' ? 'ai' : 'human',
|
|
142
|
+
content: textContent,
|
|
143
|
+
id: await uuidFromString(message.id ?? textContent),
|
|
144
|
+
isComplete: true,
|
|
145
|
+
isReasoning: false,
|
|
146
|
+
};
|
|
147
|
+
if (toolCalls?.length) {
|
|
148
|
+
dto.toolCalls = [];
|
|
149
|
+
for (const tc of toolCalls) {
|
|
150
|
+
dto.toolCalls.push({
|
|
151
|
+
name: tc.name,
|
|
152
|
+
args: tc.args,
|
|
153
|
+
id: tc.id ?? (await uuidFromString(JSON.stringify(tc.args))),
|
|
154
|
+
output: undefined,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (reasoning) dto.reasoning = reasoning;
|
|
159
|
+
if (attachment) dto.attachment = attachment;
|
|
160
|
+
if (attachments?.length) dto.attachments = attachments;
|
|
161
|
+
acc.push(dto);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (toolMsg) {
|
|
165
|
+
const kwargs = (
|
|
166
|
+
toolMsg as unknown as {
|
|
167
|
+
lc_kwargs?: { tool_call_id?: string; args?: unknown };
|
|
168
|
+
}
|
|
169
|
+
).lc_kwargs;
|
|
170
|
+
const toolCallId =
|
|
171
|
+
toolMsg.tool_call_id ??
|
|
172
|
+
kwargs?.tool_call_id ??
|
|
173
|
+
(await uuidFromString(JSON.stringify(kwargs?.args)));
|
|
174
|
+
const idx = acc.findIndex((m) =>
|
|
175
|
+
m.toolCalls?.some((t) => t.id === toolCallId),
|
|
176
|
+
);
|
|
177
|
+
const el = idx !== -1 ? acc[idx] : undefined;
|
|
178
|
+
if (el) {
|
|
179
|
+
el.toolCalls = el.toolCalls?.map((t) =>
|
|
180
|
+
t.id === toolCallId
|
|
181
|
+
? {
|
|
182
|
+
...t,
|
|
183
|
+
output: JSON.stringify(toolMsg.content),
|
|
184
|
+
status: 'done' as const,
|
|
185
|
+
}
|
|
186
|
+
: t,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { messages: acc };
|
|
192
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /messages/:id` body validation — the Workers counterpart of Node's
|
|
3
|
+
* `SendMessageDto` under a `ValidationPipe({ whitelist, forbidNonWhitelisted })`
|
|
4
|
+
* behind express body-parser: malformed JSON, unknown top-level fields and a
|
|
5
|
+
* missing `message` are all 400s, never a failed turn or a 500.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Top-level fields of Node's `SendMessageDto`; anything else is rejected. */
|
|
9
|
+
export const TURN_BODY_KEYS: ReadonlySet<string> = new Set([
|
|
10
|
+
'message',
|
|
11
|
+
'stream',
|
|
12
|
+
'returnAllMessages',
|
|
13
|
+
'model',
|
|
14
|
+
'tools',
|
|
15
|
+
'agActions',
|
|
16
|
+
'metadata',
|
|
17
|
+
'timezone',
|
|
18
|
+
'homeServer',
|
|
19
|
+
'mcpInvocations',
|
|
20
|
+
'attachments',
|
|
21
|
+
'requestId',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
export interface TurnBody {
|
|
25
|
+
message: string;
|
|
26
|
+
stream?: boolean;
|
|
27
|
+
returnAllMessages?: boolean;
|
|
28
|
+
timezone?: string;
|
|
29
|
+
model?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Free-form client metadata (Node's `SendMessageDto.metadata`); the runtime
|
|
32
|
+
* reads `editorRoomId`, `spaceId`, `sessionRunId` and `currentEntityDid`.
|
|
33
|
+
*/
|
|
34
|
+
metadata?: Record<string, unknown> & {
|
|
35
|
+
editorRoomId?: string;
|
|
36
|
+
spaceId?: string;
|
|
37
|
+
sessionRunId?: string;
|
|
38
|
+
currentEntityDid?: string;
|
|
39
|
+
};
|
|
40
|
+
tools?: Array<{
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
schema: Record<string, unknown>;
|
|
44
|
+
}>;
|
|
45
|
+
agActions?: Array<{
|
|
46
|
+
name: string;
|
|
47
|
+
description: string;
|
|
48
|
+
schema: Record<string, unknown>;
|
|
49
|
+
hasRender?: boolean;
|
|
50
|
+
}>;
|
|
51
|
+
attachments?: unknown[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type ParsedTurnBody =
|
|
55
|
+
| { ok: true; body: TurnBody }
|
|
56
|
+
| { ok: false; status: 400; message: string };
|
|
57
|
+
|
|
58
|
+
const reject = (message: string): ParsedTurnBody => ({
|
|
59
|
+
ok: false,
|
|
60
|
+
status: 400,
|
|
61
|
+
message,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/** Parse and validate a raw request body. Never throws. */
|
|
65
|
+
export function parseTurnBody(raw: string): ParsedTurnBody {
|
|
66
|
+
let parsed: unknown;
|
|
67
|
+
try {
|
|
68
|
+
parsed = JSON.parse(raw);
|
|
69
|
+
} catch {
|
|
70
|
+
return reject('Invalid JSON body');
|
|
71
|
+
}
|
|
72
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
73
|
+
return reject('Request body must be a JSON object');
|
|
74
|
+
}
|
|
75
|
+
const record = parsed as Record<string, unknown>;
|
|
76
|
+
const unknownProp = Object.keys(record).find((k) => !TURN_BODY_KEYS.has(k));
|
|
77
|
+
if (unknownProp !== undefined) {
|
|
78
|
+
return reject(`property ${unknownProp} should not exist`);
|
|
79
|
+
}
|
|
80
|
+
if (typeof record.message !== 'string' || record.message.length === 0) {
|
|
81
|
+
return reject('message is required');
|
|
82
|
+
}
|
|
83
|
+
return { ok: true, body: record as unknown as TurnBody };
|
|
84
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request `metadata` → agent state, the Node runtime's `agent-builder` rules:
|
|
3
|
+
*
|
|
4
|
+
* - `editorRoomId` / `spaceId` / `currentEntityDid` update the thread's
|
|
5
|
+
* checkpointed value when the request carries them, otherwise the prior
|
|
6
|
+
* value stands;
|
|
7
|
+
* - `sessionRunId` follows the room: a request that names the editor room
|
|
8
|
+
* also defines its session run, and "no run" must not fall back to one
|
|
9
|
+
* remembered from an earlier flow — so it is written whenever
|
|
10
|
+
* `editorRoomId` is, even as `undefined`;
|
|
11
|
+
* - an active editor context (room or space, from the request or the
|
|
12
|
+
* checkpoint) seeds the editor plugin into `loadedPlugins`, so its tools
|
|
13
|
+
* pass the capability gate without an explicit `load_capability` step.
|
|
14
|
+
*
|
|
15
|
+
* Metadata crosses the Durable Object boundary as a JSON string; only string
|
|
16
|
+
* values are honoured, anything else is ignored.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export const EDITOR_PLUGIN_NAME = 'editor';
|
|
20
|
+
|
|
21
|
+
export interface TurnMetadata {
|
|
22
|
+
editorRoomId?: string;
|
|
23
|
+
spaceId?: string;
|
|
24
|
+
sessionRunId?: string;
|
|
25
|
+
currentEntityDid?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const KEYS = [
|
|
29
|
+
'editorRoomId',
|
|
30
|
+
'spaceId',
|
|
31
|
+
'sessionRunId',
|
|
32
|
+
'currentEntityDid',
|
|
33
|
+
] as const;
|
|
34
|
+
|
|
35
|
+
export function parseTurnMetadata(json: string | undefined): TurnMetadata {
|
|
36
|
+
if (!json) return {};
|
|
37
|
+
let parsed: unknown;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(json);
|
|
40
|
+
} catch {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
44
|
+
return {};
|
|
45
|
+
const record: Record<string, unknown> = { ...parsed };
|
|
46
|
+
const out: TurnMetadata = {};
|
|
47
|
+
for (const key of KEYS) {
|
|
48
|
+
const value = record[key];
|
|
49
|
+
if (typeof value === 'string' && value.length > 0) out[key] = value;
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The checkpointed values the mapping falls back to. */
|
|
55
|
+
export interface PriorMetadataState {
|
|
56
|
+
editorRoomId?: string;
|
|
57
|
+
spaceId?: string;
|
|
58
|
+
sessionRunId?: string;
|
|
59
|
+
currentEntityDid?: string;
|
|
60
|
+
loadedPlugins?: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function priorString(
|
|
64
|
+
prior: Record<string, unknown>,
|
|
65
|
+
key: keyof PriorMetadataState,
|
|
66
|
+
): string | undefined {
|
|
67
|
+
const value = prior[key];
|
|
68
|
+
return typeof value === 'string' ? value : undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function priorMetadataState(
|
|
72
|
+
prior: Record<string, unknown>,
|
|
73
|
+
): PriorMetadataState {
|
|
74
|
+
const loaded = prior['loadedPlugins'];
|
|
75
|
+
return {
|
|
76
|
+
editorRoomId: priorString(prior, 'editorRoomId'),
|
|
77
|
+
spaceId: priorString(prior, 'spaceId'),
|
|
78
|
+
sessionRunId: priorString(prior, 'sessionRunId'),
|
|
79
|
+
currentEntityDid: priorString(prior, 'currentEntityDid'),
|
|
80
|
+
...(Array.isArray(loaded)
|
|
81
|
+
? { loadedPlugins: loaded.filter((p) => typeof p === 'string') }
|
|
82
|
+
: {}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function editorContextActive(
|
|
87
|
+
meta: TurnMetadata,
|
|
88
|
+
prior: PriorMetadataState,
|
|
89
|
+
): boolean {
|
|
90
|
+
return Boolean(
|
|
91
|
+
(meta.editorRoomId ?? prior.editorRoomId) ||
|
|
92
|
+
(meta.spaceId ?? prior.spaceId),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Fields for the build-time state (`history.state`): request over checkpoint. */
|
|
97
|
+
export function metadataBuildState(
|
|
98
|
+
meta: TurnMetadata,
|
|
99
|
+
prior: PriorMetadataState,
|
|
100
|
+
): {
|
|
101
|
+
editorRoomId?: string;
|
|
102
|
+
sessionRunId?: string;
|
|
103
|
+
spaceId?: string;
|
|
104
|
+
currentEntityDid?: string;
|
|
105
|
+
loadedPlugins: string[];
|
|
106
|
+
} {
|
|
107
|
+
const loaded = prior.loadedPlugins ?? [];
|
|
108
|
+
return {
|
|
109
|
+
editorRoomId: meta.editorRoomId ?? prior.editorRoomId,
|
|
110
|
+
sessionRunId:
|
|
111
|
+
meta.editorRoomId !== undefined ? meta.sessionRunId : prior.sessionRunId,
|
|
112
|
+
spaceId: meta.spaceId ?? prior.spaceId,
|
|
113
|
+
currentEntityDid: meta.currentEntityDid ?? prior.currentEntityDid,
|
|
114
|
+
loadedPlugins: editorContextActive(meta, prior)
|
|
115
|
+
? Array.from(new Set([...loaded, EDITOR_PLUGIN_NAME]))
|
|
116
|
+
: loaded,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Fields for the graph input: only what THIS request carries is written. */
|
|
121
|
+
export function metadataGraphInput(
|
|
122
|
+
meta: TurnMetadata,
|
|
123
|
+
prior: PriorMetadataState,
|
|
124
|
+
): {
|
|
125
|
+
editorRoomId?: string;
|
|
126
|
+
sessionRunId?: string;
|
|
127
|
+
spaceId?: string;
|
|
128
|
+
currentEntityDid?: string;
|
|
129
|
+
loadedPlugins?: string[];
|
|
130
|
+
} {
|
|
131
|
+
return {
|
|
132
|
+
...(meta.editorRoomId !== undefined && {
|
|
133
|
+
editorRoomId: meta.editorRoomId,
|
|
134
|
+
// Written even when undefined so a room without a run clears the
|
|
135
|
+
// checkpointed run id rather than keeping a stale one.
|
|
136
|
+
sessionRunId: meta.sessionRunId,
|
|
137
|
+
}),
|
|
138
|
+
...(meta.spaceId !== undefined && { spaceId: meta.spaceId }),
|
|
139
|
+
...(meta.currentEntityDid !== undefined && {
|
|
140
|
+
currentEntityDid: meta.currentEntityDid,
|
|
141
|
+
}),
|
|
142
|
+
...(editorContextActive(meta, prior) && {
|
|
143
|
+
loadedPlugins: [EDITOR_PLUGIN_NAME],
|
|
144
|
+
}),
|
|
145
|
+
};
|
|
146
|
+
}
|