@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,619 @@
|
|
|
1
|
+
import type { BotCredentials } from '../do/contracts';
|
|
2
|
+
import type { AttachmentViewSurface } from '../attachments/view';
|
|
3
|
+
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
4
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
5
|
+
import type {
|
|
6
|
+
ActionCallEventPayload,
|
|
7
|
+
BrowserToolCallEventPayload,
|
|
8
|
+
ChatOpenAIFields,
|
|
9
|
+
Logger,
|
|
10
|
+
MatrixEvent,
|
|
11
|
+
MergedConfig,
|
|
12
|
+
MessageCacheInvalidationPayload,
|
|
13
|
+
ModelRole,
|
|
14
|
+
OracleIdentity,
|
|
15
|
+
PluginContext,
|
|
16
|
+
ReadonlyState,
|
|
17
|
+
ReasoningEventPayload,
|
|
18
|
+
RenderComponentEventPayload,
|
|
19
|
+
RoomStateSnapshot,
|
|
20
|
+
RouterEventPayload,
|
|
21
|
+
RuntimeContext,
|
|
22
|
+
SecretIndex,
|
|
23
|
+
SharedAccessors,
|
|
24
|
+
ToolCallEventPayload,
|
|
25
|
+
UcanDelegation,
|
|
26
|
+
UserContextData,
|
|
27
|
+
} from '../plugin-api/types';
|
|
28
|
+
import { UcanMintUnavailableError } from '../plugin-api/ucan-errors';
|
|
29
|
+
import { NOOP_LOGGER, sweepExpired } from './utils';
|
|
30
|
+
|
|
31
|
+
// ── Adapter interfaces (implemented by the Durable Object layer) ────────────
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Per-room secrets adapter. Wraps the host's secrets service so plugins
|
|
35
|
+
* never reach for a singleton directly.
|
|
36
|
+
*/
|
|
37
|
+
export interface SecretsAdapter {
|
|
38
|
+
getIndex(roomId: string): Promise<SecretIndex>;
|
|
39
|
+
getValues(roomId: string, keys: string[]): Promise<Record<string, string>>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Blob-store adapter — short-TTL keyed store for content that should never
|
|
44
|
+
* be relayed through the LLM. Plugins reach it only via the narrow
|
|
45
|
+
* `rtCtx.blobStore` shape.
|
|
46
|
+
*/
|
|
47
|
+
export interface BlobStoreAdapter {
|
|
48
|
+
put(params: {
|
|
49
|
+
userDid: string;
|
|
50
|
+
name: string;
|
|
51
|
+
value: string;
|
|
52
|
+
ttlSeconds?: number;
|
|
53
|
+
}): Promise<string>;
|
|
54
|
+
get(params: {
|
|
55
|
+
userDid: string;
|
|
56
|
+
blobId: string;
|
|
57
|
+
}): Promise<{ name: string; value: string } | null>;
|
|
58
|
+
isValidBlobId(value: unknown): value is string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Matrix adapter exposing only scoped operations a plugin should ever need. */
|
|
62
|
+
export interface MatrixAdapter {
|
|
63
|
+
postToRoom(roomId: string, content: unknown): Promise<string>;
|
|
64
|
+
/**
|
|
65
|
+
* Post a timeline event with a caller-chosen event type. `postToRoom` is
|
|
66
|
+
* the `m.room.message` shorthand; this is the general form. Returns the
|
|
67
|
+
* new event id.
|
|
68
|
+
*/
|
|
69
|
+
postEvent(
|
|
70
|
+
roomId: string,
|
|
71
|
+
eventType: string,
|
|
72
|
+
content: object,
|
|
73
|
+
): Promise<string>;
|
|
74
|
+
getRoomState(roomId: string): Promise<RoomStateSnapshot>;
|
|
75
|
+
getEventById(roomId: string, eventId: string): Promise<MatrixEvent>;
|
|
76
|
+
/** See `MatrixGatewayObject.botCredentials`. */
|
|
77
|
+
botCredentials(): Promise<BotCredentials>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** LLM adapter — turns role tags into chat models. */
|
|
81
|
+
export interface LlmAdapter {
|
|
82
|
+
get(role: ModelRole, params?: ChatOpenAIFields): BaseChatModel;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The delegation shape the capability checks read. */
|
|
86
|
+
export type DelegationLike =
|
|
87
|
+
| { capabilities?: ReadonlyArray<{ resource: string; action: string }> }
|
|
88
|
+
| undefined;
|
|
89
|
+
|
|
90
|
+
/** UCAN adapter — capability checks and downstream invocation minting. */
|
|
91
|
+
export interface UcanAdapter {
|
|
92
|
+
hasCapability(
|
|
93
|
+
delegation: DelegationLike,
|
|
94
|
+
resource: string,
|
|
95
|
+
action: string,
|
|
96
|
+
): boolean;
|
|
97
|
+
requireCapability(
|
|
98
|
+
delegation: DelegationLike,
|
|
99
|
+
resource: string,
|
|
100
|
+
action: string,
|
|
101
|
+
): void;
|
|
102
|
+
mintInvocation(
|
|
103
|
+
userDid: string,
|
|
104
|
+
target: { did: string; capability: string },
|
|
105
|
+
opts?: { skipCache?: boolean; can?: string },
|
|
106
|
+
): Promise<string>;
|
|
107
|
+
/**
|
|
108
|
+
* Resolve a downstream service URL to its did:web identifier. Returns
|
|
109
|
+
* `null` when the document is missing or has no `id`.
|
|
110
|
+
*/
|
|
111
|
+
resolveServiceDid(serviceUrl: string): Promise<string | null>;
|
|
112
|
+
/** `true` once the oracle has its Ed25519 signing key loaded. */
|
|
113
|
+
hasSigningKey(): boolean;
|
|
114
|
+
createInvocationFromDelegation(
|
|
115
|
+
delegationCar: string,
|
|
116
|
+
serviceUrl: string,
|
|
117
|
+
capability: { can: string; with: string },
|
|
118
|
+
options?: { maxTtlSeconds?: number },
|
|
119
|
+
): Promise<{ invocation: string } | { error: string }>;
|
|
120
|
+
mintSelfSignedInvocation(
|
|
121
|
+
serviceUrl: string,
|
|
122
|
+
capability: { can: string; with: string },
|
|
123
|
+
options?: { maxTtlSeconds?: number },
|
|
124
|
+
): Promise<{ invocation: string } | { error: string }>;
|
|
125
|
+
getServiceDelegation(
|
|
126
|
+
userDid: string,
|
|
127
|
+
opts: { storeUrl: string; resource: string; requiredAbility: string },
|
|
128
|
+
): Promise<
|
|
129
|
+
| { token: string; with: string }
|
|
130
|
+
| { error: 'no-delegation' | 'store-error'; detail?: string }
|
|
131
|
+
>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Raw event payload — what callers pass before the scoped emitter adds session/request ids. */
|
|
135
|
+
export type RawEventPayload = Record<string, unknown>;
|
|
136
|
+
|
|
137
|
+
/** Low-level event sink used by the scoped emitter. */
|
|
138
|
+
export interface EmitAdapter {
|
|
139
|
+
emit(eventName: string, payload: RawEventPayload): void;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Ambient services bag captured once per isolate (or per Durable Object).
|
|
144
|
+
* Lives behind PluginContext / RuntimeContext synthesis — never exposed to
|
|
145
|
+
* plugin authors directly. `src/do` implements the adapters; `core` only
|
|
146
|
+
* consumes them through these interfaces.
|
|
147
|
+
*/
|
|
148
|
+
export interface AmbientServices {
|
|
149
|
+
config: Record<string, unknown>;
|
|
150
|
+
identity: OracleIdentity;
|
|
151
|
+
availablePlugins: ReadonlySet<string>;
|
|
152
|
+
secrets: SecretsAdapter;
|
|
153
|
+
blobStore: BlobStoreAdapter;
|
|
154
|
+
matrix: MatrixAdapter;
|
|
155
|
+
llm: LlmAdapter;
|
|
156
|
+
emit: EmitAdapter;
|
|
157
|
+
ucan: UcanAdapter;
|
|
158
|
+
logger: Logger;
|
|
159
|
+
/** Host task scheduler for the current user, when the host provides one. */
|
|
160
|
+
tasks?: import('../plugin-api/types').OracleTasksSurface;
|
|
161
|
+
/** Host user-preferences store for the current user, when provided. */
|
|
162
|
+
preferences?: import('../plugin-api/types').UserPreferencesSurface;
|
|
163
|
+
/** Host bridge to the user's browser (realtime channel), when provided. */
|
|
164
|
+
frontend?: import('../plugin-api/types').FrontendCallSurface;
|
|
165
|
+
/** Host attachment access for the current session, when provided. */
|
|
166
|
+
attachments?: AttachmentViewSurface;
|
|
167
|
+
onTurnEnd?: (dispose: () => void | Promise<void>) => void;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Scoped emitter ──────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
/** Identity of the current request — every emitted event carries these. */
|
|
173
|
+
export interface ScopeKeys {
|
|
174
|
+
sessionId: string;
|
|
175
|
+
requestId: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The seven typed emitter methods a RuntimeContext exposes via `ctx.emit`. */
|
|
179
|
+
export interface ScopedEmitter {
|
|
180
|
+
toolCall(payload: ToolCallEventPayload): void;
|
|
181
|
+
actionCall(payload: ActionCallEventPayload): void;
|
|
182
|
+
renderComponent(payload: RenderComponentEventPayload): void;
|
|
183
|
+
reasoning(payload: ReasoningEventPayload): void;
|
|
184
|
+
browserToolCall(payload: BrowserToolCallEventPayload): void;
|
|
185
|
+
router(payload: RouterEventPayload): void;
|
|
186
|
+
messageCacheInvalidation(payload: MessageCacheInvalidationPayload): void;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Event names — wire-compatible with `@ixo/oracles-events`. */
|
|
190
|
+
export const EVENT_NAMES = {
|
|
191
|
+
toolCall: 'tool_call',
|
|
192
|
+
actionCall: 'action_call',
|
|
193
|
+
renderComponent: 'render_component',
|
|
194
|
+
reasoning: 'reasoning',
|
|
195
|
+
browserToolCall: 'browser_tool_call',
|
|
196
|
+
router: 'router.update',
|
|
197
|
+
messageCacheInvalidation: 'message_cache_invalidation',
|
|
198
|
+
} as const;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Build a scoped emitter that injects the current `sessionId`/`requestId`
|
|
202
|
+
* onto every emitted payload before forwarding to the underlying sink.
|
|
203
|
+
*/
|
|
204
|
+
export function createScopedEmitter(
|
|
205
|
+
scope: ScopeKeys,
|
|
206
|
+
sink: EmitAdapter,
|
|
207
|
+
): ScopedEmitter {
|
|
208
|
+
const send = (eventName: string, payload: RawEventPayload): void => {
|
|
209
|
+
sink.emit(eventName, {
|
|
210
|
+
...payload,
|
|
211
|
+
sessionId: scope.sessionId,
|
|
212
|
+
requestId: scope.requestId,
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
toolCall: (payload) => send(EVENT_NAMES.toolCall, payload),
|
|
218
|
+
actionCall: (payload) => send(EVENT_NAMES.actionCall, payload),
|
|
219
|
+
renderComponent: (payload) => send(EVENT_NAMES.renderComponent, payload),
|
|
220
|
+
reasoning: (payload) => send(EVENT_NAMES.reasoning, payload),
|
|
221
|
+
browserToolCall: (payload) => send(EVENT_NAMES.browserToolCall, payload),
|
|
222
|
+
router: (payload) => send(EVENT_NAMES.router, payload),
|
|
223
|
+
messageCacheInvalidation: (payload) =>
|
|
224
|
+
send(EVENT_NAMES.messageCacheInvalidation, payload),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── PluginContext ───────────────────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
export interface BuildPluginContextInput<TConfig = MergedConfig> {
|
|
231
|
+
/** Merged Zod-validated env (core + every loaded plugin's `configSchema`). */
|
|
232
|
+
config: TConfig;
|
|
233
|
+
/** Identity of this oracle. */
|
|
234
|
+
identity: OracleIdentity;
|
|
235
|
+
/** Names of the plugins resolved at boot. */
|
|
236
|
+
availablePlugins: ReadonlySet<string>;
|
|
237
|
+
/** Base logger; auto-bound to the calling plugin's name when supported. */
|
|
238
|
+
logger: Logger;
|
|
239
|
+
/** Plugin name used to scope the logger. */
|
|
240
|
+
pluginName: string;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Build the boot-time PluginContext passed to plugin methods that produce
|
|
245
|
+
* tools, sub-agents, middlewares and routes. Holds no user, no session, no
|
|
246
|
+
* request data.
|
|
247
|
+
*/
|
|
248
|
+
export function buildPluginContext<TConfig = MergedConfig>(
|
|
249
|
+
input: BuildPluginContextInput<TConfig>,
|
|
250
|
+
): PluginContext<TConfig> {
|
|
251
|
+
const scopedLogger = input.logger.child
|
|
252
|
+
? input.logger.child({ plugin: input.pluginName })
|
|
253
|
+
: input.logger;
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
config: input.config,
|
|
257
|
+
identity: input.identity,
|
|
258
|
+
availablePlugins: input.availablePlugins,
|
|
259
|
+
logger: scopedLogger,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ── RuntimeContext ──────────────────────────────────────────────────────────
|
|
264
|
+
|
|
265
|
+
/** Fixed empty `shared` accessors — frozen so callers can't mutate. */
|
|
266
|
+
export const EMPTY_SHARED: SharedAccessors = Object.freeze({});
|
|
267
|
+
|
|
268
|
+
/** The user channel passed in via LangGraph's per-run context. */
|
|
269
|
+
export interface RuntimeUserContext {
|
|
270
|
+
did: string;
|
|
271
|
+
matrixUserId: string;
|
|
272
|
+
ucanDelegation: UcanDelegation;
|
|
273
|
+
timezone?: string;
|
|
274
|
+
currentTime?: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Session info threaded through from the shell / gateway. */
|
|
278
|
+
export interface RuntimeSessionContext {
|
|
279
|
+
id: string;
|
|
280
|
+
client: 'portal' | 'matrix' | 'slack';
|
|
281
|
+
wsId?: string;
|
|
282
|
+
requestId: string;
|
|
283
|
+
roomId?: string;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** What LangGraph hands us at invocation time on `runtime.context`. */
|
|
287
|
+
export interface RunConfigContext {
|
|
288
|
+
user: RuntimeUserContext;
|
|
289
|
+
session: RuntimeSessionContext;
|
|
290
|
+
/**
|
|
291
|
+
* Set on turns that run on the user's own provider (BYO). Middlewares that
|
|
292
|
+
* must behave differently per provider (the ChatGPT history sanitizer)
|
|
293
|
+
* read it — same narrow marker the Node runtime's agent builder writes.
|
|
294
|
+
*/
|
|
295
|
+
byo?: { provider: string; active: boolean };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* The `runtime` argument passed to a tool handler in LangGraph v1. We read
|
|
300
|
+
* the `context` channel, the optional `signal`, the optional `toolCall`, and
|
|
301
|
+
* the live `state` (for the true transcript).
|
|
302
|
+
*/
|
|
303
|
+
export interface RunConfig {
|
|
304
|
+
context: RunConfigContext;
|
|
305
|
+
signal?: AbortSignal;
|
|
306
|
+
toolCall?: { id?: string };
|
|
307
|
+
/**
|
|
308
|
+
* The LIVE graph state at the moment the tool is called (LangGraph's
|
|
309
|
+
* `ToolRuntime.state`). Only `messages` is read, and it is the only source
|
|
310
|
+
* of a true transcript: the build-time snapshot predates the turn. Absent
|
|
311
|
+
* on non-tool callers, which fall back to the snapshot.
|
|
312
|
+
*/
|
|
313
|
+
state?: { messages?: readonly BaseMessage[] };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Minimum shape of the graph state that build-runtime depends on. Real graphs
|
|
318
|
+
* extend this freely.
|
|
319
|
+
*/
|
|
320
|
+
export interface RuntimeStateInput {
|
|
321
|
+
messages: readonly BaseMessage[];
|
|
322
|
+
userContext?: UserContextData;
|
|
323
|
+
loadedPlugins?: ReadonlySet<string>;
|
|
324
|
+
[key: string]: unknown;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Build the per-request RuntimeContext exposed to tool handlers, sub-agent
|
|
329
|
+
* handlers and plugin-middleware hooks. Synthesizes user/session from the
|
|
330
|
+
* LangGraph runtime channel, history from state, and the rest from ambient.
|
|
331
|
+
*
|
|
332
|
+
* `sharedFactory` lets the main-agent build attach the shared-state
|
|
333
|
+
* registry's accessors (`ctx.shared.<key>`); it receives the finished context
|
|
334
|
+
* because accessors are evaluated lazily against it. Direct callers that omit
|
|
335
|
+
* it get the frozen empty bag.
|
|
336
|
+
*/
|
|
337
|
+
export function buildRuntimeContext<TConfig = MergedConfig>(
|
|
338
|
+
runConfig: RunConfig,
|
|
339
|
+
ambient: AmbientServices,
|
|
340
|
+
state: RuntimeStateInput,
|
|
341
|
+
sharedFactory?: (ctx: RuntimeContext<TConfig>) => SharedAccessors,
|
|
342
|
+
): RuntimeContext<TConfig> {
|
|
343
|
+
const session = runConfig.context.session;
|
|
344
|
+
const user = runConfig.context.user;
|
|
345
|
+
|
|
346
|
+
const messages: readonly BaseMessage[] =
|
|
347
|
+
runConfig.state?.messages ?? state.messages ?? [];
|
|
348
|
+
const userContext: UserContextData = state.userContext ?? {};
|
|
349
|
+
const loadedPlugins: ReadonlySet<string> =
|
|
350
|
+
state.loadedPlugins ?? new Set<string>();
|
|
351
|
+
|
|
352
|
+
const recent = (n: number): BaseMessage[] =>
|
|
353
|
+
n <= 0 ? [] : messages.slice(Math.max(0, messages.length - n));
|
|
354
|
+
|
|
355
|
+
const readonlyState: ReadonlyState = {
|
|
356
|
+
...state,
|
|
357
|
+
messages,
|
|
358
|
+
userContext,
|
|
359
|
+
loadedPlugins,
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const emit = createScopedEmitter(
|
|
363
|
+
{ sessionId: session.id, requestId: session.requestId },
|
|
364
|
+
ambient.emit,
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const abortSignal = runConfig.signal ?? new AbortController().signal;
|
|
368
|
+
|
|
369
|
+
const delegation = user.ucanDelegation;
|
|
370
|
+
|
|
371
|
+
const ctx: RuntimeContext<TConfig> = {
|
|
372
|
+
user,
|
|
373
|
+
session,
|
|
374
|
+
history: {
|
|
375
|
+
messages,
|
|
376
|
+
recent,
|
|
377
|
+
userContext,
|
|
378
|
+
state: readonlyState,
|
|
379
|
+
},
|
|
380
|
+
config: ambient.config as TConfig,
|
|
381
|
+
availablePlugins: ambient.availablePlugins,
|
|
382
|
+
loadedPlugins,
|
|
383
|
+
secrets: {
|
|
384
|
+
getIndex: () => {
|
|
385
|
+
if (!session.roomId) {
|
|
386
|
+
return Promise.resolve({});
|
|
387
|
+
}
|
|
388
|
+
return ambient.secrets.getIndex(session.roomId);
|
|
389
|
+
},
|
|
390
|
+
getValues: (keys: string[]) => {
|
|
391
|
+
if (!session.roomId) {
|
|
392
|
+
return Promise.resolve({});
|
|
393
|
+
}
|
|
394
|
+
return ambient.secrets.getValues(session.roomId, keys);
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
blobStore: {
|
|
398
|
+
put: (params) => ambient.blobStore.put(params),
|
|
399
|
+
get: (params) => ambient.blobStore.get(params),
|
|
400
|
+
isValidBlobId: (value): value is string =>
|
|
401
|
+
ambient.blobStore.isValidBlobId(value),
|
|
402
|
+
},
|
|
403
|
+
matrix: {
|
|
404
|
+
postToRoom: (roomId, content) =>
|
|
405
|
+
ambient.matrix.postToRoom(roomId, content),
|
|
406
|
+
botCredentials: () => ambient.matrix.botCredentials(),
|
|
407
|
+
postEvent: (roomId, eventType, content) =>
|
|
408
|
+
ambient.matrix.postEvent(roomId, eventType, content),
|
|
409
|
+
getRoomState: (roomId) => ambient.matrix.getRoomState(roomId),
|
|
410
|
+
getEventById: (roomId, eventId) =>
|
|
411
|
+
ambient.matrix.getEventById(roomId, eventId),
|
|
412
|
+
},
|
|
413
|
+
ucan: {
|
|
414
|
+
hasCapability: (resource, action) =>
|
|
415
|
+
ambient.ucan.hasCapability(delegation, resource, action),
|
|
416
|
+
requireCapability: (resource, action) => {
|
|
417
|
+
ambient.ucan.requireCapability(delegation, resource, action);
|
|
418
|
+
},
|
|
419
|
+
mintInvocation: (target, opts) =>
|
|
420
|
+
ambient.ucan.mintInvocation(user.did, target, opts),
|
|
421
|
+
resolveServiceDid: (serviceUrl) =>
|
|
422
|
+
ambient.ucan.resolveServiceDid(serviceUrl),
|
|
423
|
+
hasSigningKey: () => ambient.ucan.hasSigningKey(),
|
|
424
|
+
createInvocationFromDelegation: (car, serviceUrl, capability, opts) =>
|
|
425
|
+
ambient.ucan.createInvocationFromDelegation(
|
|
426
|
+
car,
|
|
427
|
+
serviceUrl,
|
|
428
|
+
capability,
|
|
429
|
+
opts,
|
|
430
|
+
),
|
|
431
|
+
mintSelfSignedInvocation: (serviceUrl, capability, opts) =>
|
|
432
|
+
ambient.ucan.mintSelfSignedInvocation(serviceUrl, capability, opts),
|
|
433
|
+
getServiceDelegation: (userDid, opts) =>
|
|
434
|
+
ambient.ucan.getServiceDelegation(userDid, opts),
|
|
435
|
+
},
|
|
436
|
+
llm: {
|
|
437
|
+
get: (role, params) => ambient.llm.get(role, params),
|
|
438
|
+
},
|
|
439
|
+
emit,
|
|
440
|
+
logger: ambient.logger,
|
|
441
|
+
abortSignal,
|
|
442
|
+
...(ambient.tasks ? { tasks: ambient.tasks } : {}),
|
|
443
|
+
...(ambient.preferences ? { preferences: ambient.preferences } : {}),
|
|
444
|
+
...(ambient.frontend ? { frontend: ambient.frontend } : {}),
|
|
445
|
+
...(ambient.attachments ? { attachments: ambient.attachments } : {}),
|
|
446
|
+
...(ambient.onTurnEnd ? { onTurnEnd: ambient.onTurnEnd } : {}),
|
|
447
|
+
shared: EMPTY_SHARED,
|
|
448
|
+
...(runConfig.toolCall?.id ? { toolCallId: runConfig.toolCall.id } : {}),
|
|
449
|
+
};
|
|
450
|
+
if (sharedFactory) {
|
|
451
|
+
ctx.shared = sharedFactory(ctx);
|
|
452
|
+
}
|
|
453
|
+
return ctx;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ── Defaults for tests and partial hosts ────────────────────────────────────
|
|
457
|
+
|
|
458
|
+
const BLOB_ID_RE = /^blob_[0-9a-f]{16}$/;
|
|
459
|
+
const BLOB_DEFAULT_TTL_SECONDS = 60 * 60;
|
|
460
|
+
const BLOB_MAX_TTL_SECONDS = 24 * 60 * 60;
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* In-memory blob store with the same id format and TTL clamping as the Node
|
|
464
|
+
* runtime's `BlobStoreService`. Entries are namespaced by user DID so a
|
|
465
|
+
* cross-user read always misses. Suitable for tests and as a per-isolate
|
|
466
|
+
* fallback; a production host should back it with Durable Object storage.
|
|
467
|
+
*/
|
|
468
|
+
export function createMemoryBlobStore(): BlobStoreAdapter {
|
|
469
|
+
const entries = new Map<
|
|
470
|
+
string,
|
|
471
|
+
{ name: string; value: string; expiresAt: number }
|
|
472
|
+
>();
|
|
473
|
+
const keyOf = (userDid: string, blobId: string) => `${userDid}${blobId}`;
|
|
474
|
+
const randomId = (): string => {
|
|
475
|
+
const bytes = new Uint8Array(8);
|
|
476
|
+
crypto.getRandomValues(bytes);
|
|
477
|
+
return `blob_${Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')}`;
|
|
478
|
+
};
|
|
479
|
+
return {
|
|
480
|
+
async put({ userDid, name, value, ttlSeconds }) {
|
|
481
|
+
const ttl = Math.min(
|
|
482
|
+
Math.max(1, ttlSeconds ?? BLOB_DEFAULT_TTL_SECONDS),
|
|
483
|
+
BLOB_MAX_TTL_SECONDS,
|
|
484
|
+
);
|
|
485
|
+
sweepExpired(entries);
|
|
486
|
+
const blobId = randomId();
|
|
487
|
+
entries.set(keyOf(userDid, blobId), {
|
|
488
|
+
name,
|
|
489
|
+
value,
|
|
490
|
+
expiresAt: Date.now() + ttl * 1000,
|
|
491
|
+
});
|
|
492
|
+
return blobId;
|
|
493
|
+
},
|
|
494
|
+
async get({ userDid, blobId }) {
|
|
495
|
+
const entry = entries.get(keyOf(userDid, blobId));
|
|
496
|
+
if (!entry) return null;
|
|
497
|
+
if (entry.expiresAt <= Date.now()) {
|
|
498
|
+
entries.delete(keyOf(userDid, blobId));
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
return { name: entry.name, value: entry.value };
|
|
502
|
+
},
|
|
503
|
+
isValidBlobId: (value): value is string =>
|
|
504
|
+
typeof value === 'string' && BLOB_ID_RE.test(value),
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Pure capability check over a delegation's declared capabilities. A `*`
|
|
510
|
+
* resource or action matches anything, mirroring the Node adapter.
|
|
511
|
+
*/
|
|
512
|
+
export function delegationHasCapability(
|
|
513
|
+
delegation: DelegationLike,
|
|
514
|
+
resource: string,
|
|
515
|
+
action: string,
|
|
516
|
+
): boolean {
|
|
517
|
+
const caps = delegation?.capabilities ?? [];
|
|
518
|
+
return caps.some(
|
|
519
|
+
(cap) =>
|
|
520
|
+
(cap.resource === '*' || cap.resource === resource) &&
|
|
521
|
+
(cap.action === '*' || cap.action === action),
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* UCAN adapter for hosts without a signing key: capability checks work
|
|
527
|
+
* against the delegation the user presented, every mint path reports
|
|
528
|
+
* "unavailable" through the documented non-throwing / `UcanMintUnavailableError`
|
|
529
|
+
* channels, and `hasSigningKey()` is `false` so plugins degrade to their
|
|
530
|
+
* public-only paths.
|
|
531
|
+
*/
|
|
532
|
+
export function createUnsignedUcanAdapter(): UcanAdapter {
|
|
533
|
+
const unavailable = 'UCAN minting is unavailable: no signing key loaded.';
|
|
534
|
+
return {
|
|
535
|
+
hasCapability: delegationHasCapability,
|
|
536
|
+
requireCapability(delegation, resource, action) {
|
|
537
|
+
if (!delegationHasCapability(delegation, resource, action)) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
`Missing required capability: ${action} on ${resource}`,
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
async mintInvocation() {
|
|
544
|
+
throw new UcanMintUnavailableError(unavailable);
|
|
545
|
+
},
|
|
546
|
+
async resolveServiceDid() {
|
|
547
|
+
return null;
|
|
548
|
+
},
|
|
549
|
+
hasSigningKey: () => false,
|
|
550
|
+
async createInvocationFromDelegation() {
|
|
551
|
+
return { error: unavailable };
|
|
552
|
+
},
|
|
553
|
+
async mintSelfSignedInvocation() {
|
|
554
|
+
return { error: unavailable };
|
|
555
|
+
},
|
|
556
|
+
async getServiceDelegation() {
|
|
557
|
+
return { error: 'no-delegation' as const };
|
|
558
|
+
},
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** Matrix adapter that refuses every call — for hosts without a gateway. */
|
|
563
|
+
export function createUnavailableMatrixAdapter(): MatrixAdapter {
|
|
564
|
+
const fail = (op: string) =>
|
|
565
|
+
Promise.reject(
|
|
566
|
+
new Error(`Matrix is not available in this runtime (${op}).`),
|
|
567
|
+
);
|
|
568
|
+
return {
|
|
569
|
+
postToRoom: () => fail('postToRoom'),
|
|
570
|
+
postEvent: () => fail('postEvent'),
|
|
571
|
+
getRoomState: () => fail('getRoomState'),
|
|
572
|
+
getEventById: () => fail('getEventById'),
|
|
573
|
+
botCredentials: () => fail('botCredentials'),
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** LLM adapter that throws on every role — forces tests to inject a model. */
|
|
578
|
+
export function createUnavailableLlmAdapter(): LlmAdapter {
|
|
579
|
+
return {
|
|
580
|
+
get(role) {
|
|
581
|
+
throw new Error(
|
|
582
|
+
`No LLM adapter configured (requested role "${String(role)}"). Pass \`llm\` to createNoopAmbient() or build one with createLlmAdapter().`,
|
|
583
|
+
);
|
|
584
|
+
},
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export type NoopAmbientOverrides = Partial<AmbientServices>;
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* An `AmbientServices` bag with sane defaults for tests and for hosts that
|
|
592
|
+
* do not wire every adapter: no-op secrets, an in-memory blob store,
|
|
593
|
+
* throwing Matrix, unsigned UCAN, a throwing LLM, a discarding emitter and a
|
|
594
|
+
* silent logger. Every field can be overridden.
|
|
595
|
+
*/
|
|
596
|
+
export function createNoopAmbient(
|
|
597
|
+
overrides: NoopAmbientOverrides = {},
|
|
598
|
+
): AmbientServices {
|
|
599
|
+
return {
|
|
600
|
+
config: overrides.config ?? {},
|
|
601
|
+
identity: overrides.identity ?? {
|
|
602
|
+
name: 'Oracle',
|
|
603
|
+
org: '',
|
|
604
|
+
description: '',
|
|
605
|
+
entityDid: 'did:ixo:oracle',
|
|
606
|
+
},
|
|
607
|
+
availablePlugins: overrides.availablePlugins ?? new Set<string>(),
|
|
608
|
+
secrets: overrides.secrets ?? {
|
|
609
|
+
getIndex: async () => ({}),
|
|
610
|
+
getValues: async () => ({}),
|
|
611
|
+
},
|
|
612
|
+
blobStore: overrides.blobStore ?? createMemoryBlobStore(),
|
|
613
|
+
matrix: overrides.matrix ?? createUnavailableMatrixAdapter(),
|
|
614
|
+
llm: overrides.llm ?? createUnavailableLlmAdapter(),
|
|
615
|
+
emit: overrides.emit ?? { emit: () => undefined },
|
|
616
|
+
ucan: overrides.ucan ?? createUnsignedUcanAdapter(),
|
|
617
|
+
logger: overrides.logger ?? NOOP_LOGGER,
|
|
618
|
+
};
|
|
619
|
+
}
|