@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,365 @@
|
|
|
1
|
+
import { Composio } from '@composio/core';
|
|
2
|
+
import { LangchainProvider } from '@composio/langchain';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import type { PluginTool, RuntimeContext } from '../../plugin-api/types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Minimal shape of a LangChain `DynamicStructuredTool` the composio session
|
|
8
|
+
* returns. The `schema` field is typed broadly because `DynamicStructuredTool`
|
|
9
|
+
* permits either a Zod schema or a raw JSON schema; at runtime the composio
|
|
10
|
+
* `LangchainProvider` always emits Zod (via `jsonSchemaToZodSchema`), but the
|
|
11
|
+
* structural interface stays wide so a non-conforming SDK shape doesn't crash
|
|
12
|
+
* the build. {@link toolSchema} narrows the value when wrapping each entry.
|
|
13
|
+
*/
|
|
14
|
+
export interface ComposioSessionTool {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly description: string;
|
|
17
|
+
readonly schema: unknown;
|
|
18
|
+
invoke(input: unknown): Promise<unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Factory that opens a Composio session for the current user and returns the
|
|
23
|
+
* dynamic tool list. Injected into the plugin so tests can stub the SDK out
|
|
24
|
+
* entirely.
|
|
25
|
+
*/
|
|
26
|
+
export type ComposioSessionFactory = (
|
|
27
|
+
args: ComposioSessionFactoryArgs,
|
|
28
|
+
) => Promise<ComposioSessionTool[]>;
|
|
29
|
+
|
|
30
|
+
export interface ComposioSessionFactoryArgs {
|
|
31
|
+
/** Resolved at boot from the plugin's configSchema. */
|
|
32
|
+
apiKey: string;
|
|
33
|
+
/** Resolved at boot from the plugin's configSchema. */
|
|
34
|
+
baseUrl: string;
|
|
35
|
+
/** UCAN invocation token, minted per request — forwarded as a header. */
|
|
36
|
+
ucanInvocation: string;
|
|
37
|
+
/** User identifier; composio uses it as the session `user_id`. */
|
|
38
|
+
userId: string;
|
|
39
|
+
/** Optional `x-ixo-network` header value forwarded to composio. */
|
|
40
|
+
network?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Default {@link ComposioSessionFactory}: opens a real Composio session using
|
|
45
|
+
* `@composio/core` + `@composio/langchain`.
|
|
46
|
+
*/
|
|
47
|
+
export function createDefaultComposioSessionFactory(): ComposioSessionFactory {
|
|
48
|
+
return async ({ apiKey, baseUrl, ucanInvocation, userId, network }) => {
|
|
49
|
+
const defaultHeaders: Record<string, string> = {
|
|
50
|
+
'x-ucan-invocation': ucanInvocation,
|
|
51
|
+
};
|
|
52
|
+
if (network) defaultHeaders['x-ixo-network'] = network;
|
|
53
|
+
|
|
54
|
+
const composio = new Composio({
|
|
55
|
+
apiKey,
|
|
56
|
+
provider: new LangchainProvider(),
|
|
57
|
+
baseURL: baseUrl,
|
|
58
|
+
defaultHeaders,
|
|
59
|
+
});
|
|
60
|
+
const session = await composio.create(userId);
|
|
61
|
+
const tools = await session.tools();
|
|
62
|
+
return tools;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Tool *definition* — the session-independent slice of a
|
|
68
|
+
* {@link ComposioSessionTool}. The per-user/per-request part (the UCAN
|
|
69
|
+
* invocation header baked into the session) is applied at invoke time.
|
|
70
|
+
*/
|
|
71
|
+
export interface ComposioToolDef {
|
|
72
|
+
name: string;
|
|
73
|
+
description: string;
|
|
74
|
+
schema: unknown;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface CachedComposioDefs {
|
|
78
|
+
defs: ComposioToolDef[];
|
|
79
|
+
expiresAt: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Cache of session tool definitions, keyed per user (see {@link defsCacheKey}). */
|
|
83
|
+
export type ComposioDefsCache = Map<string, CachedComposioDefs>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The composio session surface is a stable meta-toolset (search / execute /
|
|
87
|
+
* manage-connections); it shifts only when the user's connected-account state
|
|
88
|
+
* flips. A short TTL keeps that fresh while removing the session-create +
|
|
89
|
+
* tools-list round-trips from nearly every chat turn.
|
|
90
|
+
*/
|
|
91
|
+
export const COMPOSIO_TOOL_DEFS_TTL_MS = 5 * 60 * 1000;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Hard ceiling on cached users. The expired-entry sweep is what bounds the
|
|
95
|
+
* cache in practice (entries outlive their user by at most one TTL); the cap
|
|
96
|
+
* only kicks in if more distinct users than this are active inside a single
|
|
97
|
+
* TTL window, evicting the soonest-to-expire entries first.
|
|
98
|
+
*/
|
|
99
|
+
export const COMPOSIO_DEFS_CACHE_MAX_ENTRIES = 1000;
|
|
100
|
+
|
|
101
|
+
function defsCacheKey(baseUrl: string, userId: string): string {
|
|
102
|
+
return `${baseUrl}::${userId}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Cache keys with a background definition refresh in flight — guards a burst
|
|
107
|
+
* of turns from each opening its own session when an entry expires.
|
|
108
|
+
*/
|
|
109
|
+
const refreshInFlight = new Set<string>();
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Drop long-expired entries (and, if still over the cap, the
|
|
113
|
+
* soonest-to-expire ones). Runs on every cache write, so in a long-running
|
|
114
|
+
* multi-tenant isolate one-off users' schemas are reclaimed instead of
|
|
115
|
+
* accumulating for the isolate lifetime. Entries get one extra TTL of grace
|
|
116
|
+
* past expiry — a recently-expired entry is still served stale while its
|
|
117
|
+
* background refresh runs, so reclaiming it immediately would put the
|
|
118
|
+
* session-open round-trip back on that user's next turn.
|
|
119
|
+
*/
|
|
120
|
+
function pruneDefsCache(cache: ComposioDefsCache, now: number): void {
|
|
121
|
+
for (const [key, entry] of cache) {
|
|
122
|
+
if (entry.expiresAt + COMPOSIO_TOOL_DEFS_TTL_MS <= now) cache.delete(key);
|
|
123
|
+
}
|
|
124
|
+
const surplus = cache.size - COMPOSIO_DEFS_CACHE_MAX_ENTRIES;
|
|
125
|
+
if (surplus <= 0) return;
|
|
126
|
+
const soonestFirst = [...cache.entries()]
|
|
127
|
+
.sort((a, b) => a[1].expiresAt - b[1].expiresAt)
|
|
128
|
+
.slice(0, surplus);
|
|
129
|
+
for (const [key] of soonestFirst) cache.delete(key);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface CreateComposioToolsOptions {
|
|
133
|
+
apiKey: string;
|
|
134
|
+
baseUrl: string;
|
|
135
|
+
ucanInvocation: string;
|
|
136
|
+
userId: string;
|
|
137
|
+
network?: string;
|
|
138
|
+
/** Override the session factory — defaults to the real SDK client. */
|
|
139
|
+
sessionFactory?: ComposioSessionFactory;
|
|
140
|
+
/**
|
|
141
|
+
* Optional definitions cache (owned by the plugin instance). When provided
|
|
142
|
+
* and warm, the session open is deferred to the first actual tool
|
|
143
|
+
* invocation; when absent, every call opens a session — the pre-cache
|
|
144
|
+
* behaviour.
|
|
145
|
+
*/
|
|
146
|
+
defsCache?: ComposioDefsCache;
|
|
147
|
+
/** Logger for background-refresh failures; defaults to silent. */
|
|
148
|
+
logger?: { warn(message: string): void };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Permissive fallback schema for composio tools whose `schema` field is not
|
|
153
|
+
* a Zod schema (e.g. a raw JSON schema from a non-default provider). Used by
|
|
154
|
+
* {@link toolSchema} when narrowing fails so the agent still sees the tool
|
|
155
|
+
* with a valid descriptor — the composio backend itself validates args.
|
|
156
|
+
*/
|
|
157
|
+
const FALLBACK_ARGS_SCHEMA = z.record(z.string(), z.unknown());
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Narrow a composio session tool's `schema` field to a Zod schema.
|
|
161
|
+
*
|
|
162
|
+
* The default `LangchainProvider` always converts the upstream JSON Schema to
|
|
163
|
+
* a Zod schema, so this branch is the hot path. Custom providers that emit
|
|
164
|
+
* raw JSON Schema (rare) fall through to the permissive record schema so the
|
|
165
|
+
* tool is still discoverable.
|
|
166
|
+
*/
|
|
167
|
+
function toolSchema(schema: unknown): z.ZodType {
|
|
168
|
+
return schema instanceof z.ZodType ? schema : FALLBACK_ARGS_SCHEMA;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Composio tool-router meta-tool that batches one or more tool executions. */
|
|
172
|
+
const MULTI_EXECUTE_TOOL = 'COMPOSIO_MULTI_EXECUTE_TOOL';
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Coerce the model's `COMPOSIO_MULTI_EXECUTE_TOOL` input to the exact envelope
|
|
176
|
+
* Composio's (strict) schema accepts: `{ tools, sync_response_to_workbench }`.
|
|
177
|
+
*
|
|
178
|
+
* The multi-execute meta-tool is the one piece of the tool-router flow the
|
|
179
|
+
* model keeps malforming — across runs it has dropped the required
|
|
180
|
+
* `sync_response_to_workbench` control flag and hallucinated extra top-level
|
|
181
|
+
* keys (e.g. a spurious `session`). Prompt guidance reduces but doesn't
|
|
182
|
+
* eliminate this. Rebuilding the envelope here is deterministic:
|
|
183
|
+
*
|
|
184
|
+
* - keep the model's `tools` payload verbatim — the only part it must
|
|
185
|
+
* genuinely author (a genuinely-empty call still fails upstream because
|
|
186
|
+
* `tools` is required, which is correct: there is nothing to run);
|
|
187
|
+
* - default `sync_response_to_workbench` to `false` (return each result
|
|
188
|
+
* inline rather than offloading to the remote workbench) unless the model
|
|
189
|
+
* set it explicitly;
|
|
190
|
+
* - drop every other top-level key.
|
|
191
|
+
*
|
|
192
|
+
* This pins the envelope *shape* — a stable meta-tool contract — not any tool
|
|
193
|
+
* slugs, which live in Composio's registry and must not be hardcoded here.
|
|
194
|
+
*/
|
|
195
|
+
function isArgsRecord(value: unknown): value is Record<string, unknown> {
|
|
196
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function normalizeArgs(toolName: string, args: unknown): unknown {
|
|
200
|
+
if (toolName !== MULTI_EXECUTE_TOOL || !isArgsRecord(args)) {
|
|
201
|
+
return args;
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
tools: args.tools,
|
|
205
|
+
sync_response_to_workbench:
|
|
206
|
+
typeof args.sync_response_to_workbench === 'boolean'
|
|
207
|
+
? args.sync_response_to_workbench
|
|
208
|
+
: false,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Wrap a single composio session tool as a {@link PluginTool}. The handler
|
|
214
|
+
* forwards the args through the underlying `invoke` and propagates errors so
|
|
215
|
+
* the agent surfaces a clean failure rather than a silent empty response.
|
|
216
|
+
*/
|
|
217
|
+
function wrapAsPluginTool(sessionTool: ComposioSessionTool): PluginTool {
|
|
218
|
+
return {
|
|
219
|
+
name: sessionTool.name,
|
|
220
|
+
description: sessionTool.description,
|
|
221
|
+
schema: toolSchema(sessionTool.schema),
|
|
222
|
+
handler: async (args, ctx: RuntimeContext) => {
|
|
223
|
+
try {
|
|
224
|
+
return await sessionTool.invoke(normalizeArgs(sessionTool.name, args));
|
|
225
|
+
} catch (error) {
|
|
226
|
+
const detail =
|
|
227
|
+
error instanceof Error
|
|
228
|
+
? `${error.name}: ${error.message}`
|
|
229
|
+
: String(error);
|
|
230
|
+
ctx.logger.error(
|
|
231
|
+
`[composio] tool "${sessionTool.name}" failed: ${detail}`,
|
|
232
|
+
);
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Bind cached definitions to a lazily-opened session. The session (with this
|
|
241
|
+
* request's UCAN invocation) is created on the first actual invocation and
|
|
242
|
+
* shared by every composio tool in the request — turns that never call
|
|
243
|
+
* composio pay zero composio round-trips.
|
|
244
|
+
*/
|
|
245
|
+
function buildLazySessionTools(
|
|
246
|
+
defs: ComposioToolDef[],
|
|
247
|
+
sessionArgs: ComposioSessionFactoryArgs,
|
|
248
|
+
sessionFactory: ComposioSessionFactory,
|
|
249
|
+
): PluginTool[] {
|
|
250
|
+
let sessionByName: Promise<Map<string, ComposioSessionTool>> | null = null;
|
|
251
|
+
const connect = (): Promise<Map<string, ComposioSessionTool>> => {
|
|
252
|
+
if (!sessionByName) {
|
|
253
|
+
sessionByName = sessionFactory(sessionArgs).then(
|
|
254
|
+
(tools) => new Map(tools.map((t) => [t.name, t])),
|
|
255
|
+
);
|
|
256
|
+
// A failed session open must not poison the rest of the run — clear
|
|
257
|
+
// the memo so a later invocation retries with a fresh session.
|
|
258
|
+
sessionByName.catch(() => {
|
|
259
|
+
sessionByName = null;
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return sessionByName;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
return defs.map((def) =>
|
|
266
|
+
wrapAsPluginTool({
|
|
267
|
+
name: def.name,
|
|
268
|
+
description: def.description,
|
|
269
|
+
schema: def.schema,
|
|
270
|
+
invoke: async (input: unknown) => {
|
|
271
|
+
const byName = await connect();
|
|
272
|
+
const sessionTool = byName.get(def.name);
|
|
273
|
+
if (!sessionTool) {
|
|
274
|
+
throw new Error(
|
|
275
|
+
`composio no longer exposes "${def.name}" — cached definition is stale, retry shortly.`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
return sessionTool.invoke(input);
|
|
279
|
+
},
|
|
280
|
+
}),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Build the composio plugin's request-time tool list.
|
|
286
|
+
*
|
|
287
|
+
* 1. Opens a composio session via the configured factory (or, with a warm
|
|
288
|
+
* `defsCache`, defers the open to the first actual invocation).
|
|
289
|
+
* 2. Wraps each returned tool into a {@link PluginTool} that proxies args
|
|
290
|
+
* to the session tool's `invoke`.
|
|
291
|
+
*
|
|
292
|
+
* Returns an empty list if the session factory yields no tools (e.g. the
|
|
293
|
+
* user has no connected accounts and composio omits the management tools).
|
|
294
|
+
*/
|
|
295
|
+
export async function createComposioTools(
|
|
296
|
+
opts: CreateComposioToolsOptions,
|
|
297
|
+
): Promise<PluginTool[]> {
|
|
298
|
+
const sessionFactory =
|
|
299
|
+
opts.sessionFactory ?? createDefaultComposioSessionFactory();
|
|
300
|
+
|
|
301
|
+
const sessionArgs: ComposioSessionFactoryArgs = {
|
|
302
|
+
apiKey: opts.apiKey,
|
|
303
|
+
baseUrl: opts.baseUrl,
|
|
304
|
+
ucanInvocation: opts.ucanInvocation,
|
|
305
|
+
userId: opts.userId,
|
|
306
|
+
network: opts.network,
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const cacheKey = defsCacheKey(opts.baseUrl, opts.userId);
|
|
310
|
+
const cached = opts.defsCache?.get(cacheKey);
|
|
311
|
+
if (cached) {
|
|
312
|
+
// An expired entry is served as-is (the session meta-toolset only shifts
|
|
313
|
+
// when the user's connected-account state flips) while a background
|
|
314
|
+
// refresh re-snapshots it — TTL expiry never puts the session-open +
|
|
315
|
+
// tools-list round-trips back on a chat turn.
|
|
316
|
+
const defsCache = opts.defsCache;
|
|
317
|
+
if (
|
|
318
|
+
defsCache &&
|
|
319
|
+
cached.expiresAt <= Date.now() &&
|
|
320
|
+
!refreshInFlight.has(cacheKey)
|
|
321
|
+
) {
|
|
322
|
+
refreshInFlight.add(cacheKey);
|
|
323
|
+
void sessionFactory(sessionArgs)
|
|
324
|
+
.then((sessionTools) => {
|
|
325
|
+
defsCache.set(cacheKey, {
|
|
326
|
+
defs: sessionTools.map(({ name, description, schema }) => ({
|
|
327
|
+
name,
|
|
328
|
+
description,
|
|
329
|
+
schema,
|
|
330
|
+
})),
|
|
331
|
+
expiresAt: Date.now() + COMPOSIO_TOOL_DEFS_TTL_MS,
|
|
332
|
+
});
|
|
333
|
+
pruneDefsCache(defsCache, Date.now());
|
|
334
|
+
})
|
|
335
|
+
.catch((err: unknown) => {
|
|
336
|
+
// Keep serving the stale defs; the next expired-cache turn
|
|
337
|
+
// retries the refresh.
|
|
338
|
+
opts.logger?.warn(
|
|
339
|
+
`[composio] background tool-defs refresh failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
340
|
+
);
|
|
341
|
+
})
|
|
342
|
+
.finally(() => {
|
|
343
|
+
refreshInFlight.delete(cacheKey);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
return buildLazySessionTools(cached.defs, sessionArgs, sessionFactory);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const sessionTools = await sessionFactory(sessionArgs);
|
|
350
|
+
if (opts.defsCache) {
|
|
351
|
+
opts.defsCache.set(cacheKey, {
|
|
352
|
+
defs: sessionTools.map(({ name, description, schema }) => ({
|
|
353
|
+
name,
|
|
354
|
+
description,
|
|
355
|
+
schema,
|
|
356
|
+
})),
|
|
357
|
+
expiresAt: Date.now() + COMPOSIO_TOOL_DEFS_TTL_MS,
|
|
358
|
+
});
|
|
359
|
+
// Prune after the write so the cap accounts for the entry just added —
|
|
360
|
+
// the fresh entry has the latest expiry, so it always survives.
|
|
361
|
+
pruneDefsCache(opts.defsCache, Date.now());
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return sessionTools.map(wrapAsPluginTool);
|
|
365
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { RuntimeContext } from '../../plugin-api/types';
|
|
2
|
+
import { mintInvocationSafely, resolveServiceDidSafely } from '../ucan-failure';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mint a UCAN invocation token addressed to the composio-worker.
|
|
6
|
+
*
|
|
7
|
+
* 1. Resolves the composio-worker DID via `runCtx.ucan.resolveServiceDid`
|
|
8
|
+
* (delegates the did:web lookup + cache to the UCAN service).
|
|
9
|
+
* 2. Mints an `ixo:sandbox` invocation using the user's signing key.
|
|
10
|
+
*
|
|
11
|
+
* Returns `null` when DID resolution fails, when minting throws, or when no
|
|
12
|
+
* signing material is available. Every failure is contained inside the shared
|
|
13
|
+
* UCAN helpers — nothing throws past this boundary. Callers degrade silently:
|
|
14
|
+
* the composio client is simply not constructed and the plugin contributes no
|
|
15
|
+
* tools. The "no delegation present" case logs at `debug`; a genuine
|
|
16
|
+
* resolve/mint failure logs at `warn`.
|
|
17
|
+
*/
|
|
18
|
+
export async function mintComposioInvocation(
|
|
19
|
+
runCtx: RuntimeContext,
|
|
20
|
+
composioBaseUrl: string,
|
|
21
|
+
): Promise<string | null> {
|
|
22
|
+
const composioDid = await resolveServiceDidSafely(
|
|
23
|
+
runCtx,
|
|
24
|
+
composioBaseUrl,
|
|
25
|
+
'composio',
|
|
26
|
+
);
|
|
27
|
+
if (!composioDid) return null;
|
|
28
|
+
|
|
29
|
+
// skipCache: the composio-worker enforces single-use replay protection
|
|
30
|
+
// per invocation CID (see composio-worker `lib/validation.ts`
|
|
31
|
+
// `createInvocationStore`, KV-backed, 120s default TTL). A cached
|
|
32
|
+
// invocation would be replayed on the second request and rejected with
|
|
33
|
+
// `401 REPLAY: Invocation has already been used`. Mint fresh every call.
|
|
34
|
+
return mintInvocationSafely(
|
|
35
|
+
runCtx,
|
|
36
|
+
{ did: composioDid, capability: 'ixo:sandbox' },
|
|
37
|
+
'composio',
|
|
38
|
+
// can: composio routes through the sandbox capability, and the user's
|
|
39
|
+
// delegation grants `sandbox/*`. A `'*'` claim is satisfiable only by a
|
|
40
|
+
// `'*'` grant — `'*'.startsWith('sandbox/')` is false.
|
|
41
|
+
{ skipCache: true, can: 'sandbox/*' },
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { OraclePlugin, type PluginEnv } from '../../plugin-api/oracle-plugin';
|
|
3
|
+
import type {
|
|
4
|
+
PluginManifest,
|
|
5
|
+
PluginTool,
|
|
6
|
+
RuntimeContext,
|
|
7
|
+
} from '../../plugin-api/types';
|
|
8
|
+
import {
|
|
9
|
+
createComposioTools,
|
|
10
|
+
type ComposioDefsCache,
|
|
11
|
+
type ComposioSessionFactory,
|
|
12
|
+
} from './composio-tools';
|
|
13
|
+
import { mintComposioInvocation } from './composio-ucan';
|
|
14
|
+
|
|
15
|
+
const configSchema = z.object({
|
|
16
|
+
COMPOSIO_API_KEY: z.string().min(1, 'COMPOSIO_API_KEY must not be empty.'),
|
|
17
|
+
COMPOSIO_BASE_URL: z
|
|
18
|
+
.string()
|
|
19
|
+
.url('COMPOSIO_BASE_URL must be a valid HTTP(S) URL.')
|
|
20
|
+
.default('https://composio.ixo.earth'),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Sibling env vars the plugin reads but does not own. `NETWORK` is declared
|
|
25
|
+
* in the core base schema; the plugin forwards it as `x-ixo-network` when
|
|
26
|
+
* present so the composio-worker can route to the right IXO environment.
|
|
27
|
+
*/
|
|
28
|
+
const siblingEnvSchema = z.object({
|
|
29
|
+
NETWORK: z.string().optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const manifest: PluginManifest = {
|
|
33
|
+
title: 'Composio',
|
|
34
|
+
summary:
|
|
35
|
+
'Hundreds of integrations in one plugin: web search, news, finance, academic research, Gmail, GitHub, Linear, Slack, Google Calendar, Notion, Jira, HubSpot, and much more — all invoked on behalf of the user through Composio. When in doubt, Composio likely has a tool for it.',
|
|
36
|
+
whenToUse: [
|
|
37
|
+
'Composio exposes only a few directly-callable meta-tools: `COMPOSIO_MANAGE_CONNECTIONS` (auth), `COMPOSIO_SEARCH_TOOLS` (discover), and `COMPOSIO_MULTI_EXECUTE_TOOL` (run). Specific app tools (e.g. `GMAIL_SEND_EMAIL`, `COMPOSIO_SEARCH_FINANCE`) are NOT directly callable — discover them first, then run them through `COMPOSIO_MULTI_EXECUTE_TOOL`.',
|
|
38
|
+
'Standard flow: (1) for any connected-app action call `COMPOSIO_MANAGE_CONNECTIONS` with the toolkit FIRST — if it returns a `redirect_url`, show it as a clickable markdown link and stop; (2) call `COMPOSIO_SEARCH_TOOLS`, describing the action in natural language, to find the exact tool slug(s); (3) if unsure of a tool’s parameters, call `COMPOSIO_GET_TOOL_SCHEMAS` with those slugs to fetch their exact input schema; (4) call `COMPOSIO_MULTI_EXECUTE_TOOL` to execute.',
|
|
39
|
+
'CRITICAL — `COMPOSIO_MULTI_EXECUTE_TOOL` args are EXACTLY `{ "tools": [{ "tool_slug": "<SLUG>", "arguments": { ... } }], "sync_response_to_workbench": false }` and nothing else — no `session`, `id`, or other top-level keys. NEVER pass a tool name as a top-level key (e.g. `{ "COMPOSIO_SEARCH_FINANCE": {...} }`); always wrap each call inside the `tools` array as `tool_slug` + `arguments`.',
|
|
40
|
+
'Web, news, finance, academic, and trend searches — and fetching the readable content of a URL — live in the `COMPOSIO_SEARCH_*` family. They need no connection: discover the one you want via `COMPOSIO_SEARCH_TOOLS`, then run it through `COMPOSIO_MULTI_EXECUTE_TOOL`.',
|
|
41
|
+
'Email, calendar, issue trackers, docs, CRMs (Gmail, Outlook, Google Calendar, GitHub, Linear, Jira, Notion, Slack, HubSpot, …): verify the connection, discover the tool, then execute.',
|
|
42
|
+
'No native skill covers the requested action — discover what Composio offers with `COMPOSIO_SEARCH_TOOLS` before giving up.',
|
|
43
|
+
],
|
|
44
|
+
whenNotToUse: [
|
|
45
|
+
'A native skill or sub-agent already covers the action more precisely — prefer the skill.',
|
|
46
|
+
'Scraping a complex page that needs JavaScript rendering or main-content extraction — use the Firecrawl sub-agent instead.',
|
|
47
|
+
'IXO entity lookups — use the Domain Indexer.',
|
|
48
|
+
'Normal conversation or general question with no search or external SaaS interaction.',
|
|
49
|
+
'NEVER write, guess, or fabricate any URL yourself — the only valid auth link is the `redirect_url` returned by `COMPOSIO_MANAGE_CONNECTIONS`. Typing a link from memory is forbidden.',
|
|
50
|
+
],
|
|
51
|
+
examples: [
|
|
52
|
+
{
|
|
53
|
+
user: 'What is the current Bitcoin price?',
|
|
54
|
+
thought:
|
|
55
|
+
'A search task — no connection needed. Discover the right search tool first.',
|
|
56
|
+
tool: 'COMPOSIO_SEARCH_TOOLS',
|
|
57
|
+
args: {
|
|
58
|
+
query: 'search the web for live financial / crypto market prices',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
user: '(after COMPOSIO_SEARCH_TOOLS surfaces COMPOSIO_SEARCH_FINANCE)',
|
|
63
|
+
thought:
|
|
64
|
+
'Run the discovered tool through the multi-execute meta-tool — wrapped in the tools array, never as a top-level key.',
|
|
65
|
+
tool: 'COMPOSIO_MULTI_EXECUTE_TOOL',
|
|
66
|
+
args: {
|
|
67
|
+
tools: [
|
|
68
|
+
{
|
|
69
|
+
tool_slug: 'COMPOSIO_SEARCH_FINANCE',
|
|
70
|
+
arguments: { query: 'Bitcoin price USD today' },
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
sync_response_to_workbench: false,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
user: 'Create a Linear issue for this bug',
|
|
78
|
+
thought:
|
|
79
|
+
'A SaaS action — verify Linear is connected before discovering or running any Linear tool.',
|
|
80
|
+
tool: 'COMPOSIO_MANAGE_CONNECTIONS',
|
|
81
|
+
args: { toolkit: 'linear' },
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
user: 'Send an email to the team',
|
|
85
|
+
thought:
|
|
86
|
+
'A SaaS action — verify Gmail is connected first, then discover GMAIL_SEND_EMAIL and run it via COMPOSIO_MULTI_EXECUTE_TOOL.',
|
|
87
|
+
tool: 'COMPOSIO_MANAGE_CONNECTIONS',
|
|
88
|
+
args: { toolkit: 'gmail' },
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
tags: [
|
|
92
|
+
'composio',
|
|
93
|
+
'integration',
|
|
94
|
+
'saas',
|
|
95
|
+
'tools',
|
|
96
|
+
'web-search',
|
|
97
|
+
'news',
|
|
98
|
+
'finance',
|
|
99
|
+
],
|
|
100
|
+
category: 'integration',
|
|
101
|
+
visibility: 'on-demand',
|
|
102
|
+
stability: 'stable',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export interface ComposioPluginOptions {
|
|
106
|
+
/**
|
|
107
|
+
* Override the session factory — primarily for tests so they can skip the
|
|
108
|
+
* real `@composio/core` client and the network call it makes.
|
|
109
|
+
*/
|
|
110
|
+
sessionFactory?: ComposioSessionFactory;
|
|
111
|
+
/**
|
|
112
|
+
* Override the UCAN minting step — primarily for tests so they can return
|
|
113
|
+
* a fixed token without invoking the real UCAN service.
|
|
114
|
+
*/
|
|
115
|
+
mintInvocation?: (
|
|
116
|
+
runCtx: RuntimeContext,
|
|
117
|
+
baseUrl: string,
|
|
118
|
+
) => Promise<string | null>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Composio plugin.
|
|
123
|
+
*
|
|
124
|
+
* Tools are discovered dynamically per request: the plugin mints a UCAN
|
|
125
|
+
* invocation addressed to the composio-worker, opens a session for the
|
|
126
|
+
* current user, and exposes each returned tool to the agent.
|
|
127
|
+
*
|
|
128
|
+
* Auth is UCAN-only — no Matrix-OpenID fallback. If minting fails (no
|
|
129
|
+
* signing key, no cached delegation, did:web unresolved) the plugin
|
|
130
|
+
* contributes zero tools and the agent simply does not see composio that
|
|
131
|
+
* request. Visibility is `on-demand` so the plugin is discoverable via
|
|
132
|
+
* `list_capabilities` rather than burning prompt budget every call.
|
|
133
|
+
*/
|
|
134
|
+
export class ComposioPlugin extends OraclePlugin {
|
|
135
|
+
readonly name = 'composio';
|
|
136
|
+
|
|
137
|
+
readonly version = '1.0.0';
|
|
138
|
+
|
|
139
|
+
readonly manifest = manifest;
|
|
140
|
+
|
|
141
|
+
override readonly configSchema = configSchema;
|
|
142
|
+
|
|
143
|
+
override readonly autoDetectHint = 'COMPOSIO_API_KEY';
|
|
144
|
+
|
|
145
|
+
private readonly sessionFactoryOverride?: ComposioSessionFactory;
|
|
146
|
+
private readonly mintInvocationOverride?: (
|
|
147
|
+
runCtx: RuntimeContext,
|
|
148
|
+
baseUrl: string,
|
|
149
|
+
) => Promise<string | null>;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Per-user session tool definitions. Warm entries let `createComposioTools`
|
|
153
|
+
* skip the session-open + tools-list round-trips on the chat hot path and
|
|
154
|
+
* open the session lazily on first invocation instead.
|
|
155
|
+
*/
|
|
156
|
+
private readonly toolDefsCache: ComposioDefsCache = new Map();
|
|
157
|
+
|
|
158
|
+
constructor(opts: ComposioPluginOptions = {}) {
|
|
159
|
+
super();
|
|
160
|
+
this.sessionFactoryOverride = opts.sessionFactory;
|
|
161
|
+
this.mintInvocationOverride = opts.mintInvocation;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
override autoDetect(env: PluginEnv): boolean {
|
|
165
|
+
return (
|
|
166
|
+
typeof env.COMPOSIO_API_KEY === 'string' &&
|
|
167
|
+
env.COMPOSIO_API_KEY.length > 0
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
override async getRequestTools(rtCtx: RuntimeContext): Promise<PluginTool[]> {
|
|
172
|
+
const parsed = configSchema.safeParse(rtCtx.config);
|
|
173
|
+
if (!parsed.success) {
|
|
174
|
+
rtCtx.logger.warn(
|
|
175
|
+
`[composio] skipping — invalid configuration: ${parsed.error.issues
|
|
176
|
+
.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`)
|
|
177
|
+
.join('; ')}`,
|
|
178
|
+
);
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const siblings = siblingEnvSchema.safeParse(rtCtx.config);
|
|
183
|
+
const network = siblings.success ? siblings.data.NETWORK : undefined;
|
|
184
|
+
|
|
185
|
+
const mint = this.mintInvocationOverride ?? mintComposioInvocation;
|
|
186
|
+
const ucanInvocation = await mint(rtCtx, parsed.data.COMPOSIO_BASE_URL);
|
|
187
|
+
if (!ucanInvocation) {
|
|
188
|
+
rtCtx.logger.warn(
|
|
189
|
+
'[composio] skipping — UCAN invocation could not be minted.',
|
|
190
|
+
);
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
return await createComposioTools({
|
|
196
|
+
apiKey: parsed.data.COMPOSIO_API_KEY,
|
|
197
|
+
baseUrl: parsed.data.COMPOSIO_BASE_URL,
|
|
198
|
+
ucanInvocation,
|
|
199
|
+
userId: rtCtx.user.did,
|
|
200
|
+
network,
|
|
201
|
+
sessionFactory: this.sessionFactoryOverride,
|
|
202
|
+
defsCache: this.toolDefsCache,
|
|
203
|
+
logger: rtCtx.logger,
|
|
204
|
+
});
|
|
205
|
+
} catch (error) {
|
|
206
|
+
const detail =
|
|
207
|
+
error instanceof Error
|
|
208
|
+
? `${error.name}: ${error.message}`
|
|
209
|
+
: String(error);
|
|
210
|
+
rtCtx.logger.error(`[composio] failed to load tools: ${detail}`);
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
COMPOSIO_DEFS_CACHE_MAX_ENTRIES,
|
|
3
|
+
COMPOSIO_TOOL_DEFS_TTL_MS,
|
|
4
|
+
createComposioTools,
|
|
5
|
+
createDefaultComposioSessionFactory,
|
|
6
|
+
type CachedComposioDefs,
|
|
7
|
+
type ComposioDefsCache,
|
|
8
|
+
type ComposioSessionFactory,
|
|
9
|
+
type ComposioSessionFactoryArgs,
|
|
10
|
+
type ComposioSessionTool,
|
|
11
|
+
type ComposioToolDef,
|
|
12
|
+
type CreateComposioToolsOptions,
|
|
13
|
+
} from './composio-tools';
|
|
14
|
+
export { mintComposioInvocation } from './composio-ucan';
|
|
15
|
+
export { ComposioPlugin, type ComposioPluginOptions } from './composio.plugin';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { PluginSubAgent, PluginTool } from '../../plugin-api/types';
|
|
2
|
+
|
|
3
|
+
const formatToolDocs = (tools: PluginTool[]): string =>
|
|
4
|
+
tools
|
|
5
|
+
.map((t) => {
|
|
6
|
+
const description = t.description?.trim() ?? 'No description provided.';
|
|
7
|
+
return `- \`${t.name}\`: ${description}`;
|
|
8
|
+
})
|
|
9
|
+
.join('\n');
|
|
10
|
+
|
|
11
|
+
const buildSystemPrompt = (tools: PluginTool[]): string =>
|
|
12
|
+
`
|
|
13
|
+
You are the Domain Indexer Agent. You are the specialist for searching the IXO Domain Indexer—treat it like Google for IXO entities (organizations, projects, DAOs, agents, compositions, events).
|
|
14
|
+
|
|
15
|
+
Core expectations:
|
|
16
|
+
- Always clarify the user’s goal and translate it into a concrete search query or DID lookup.
|
|
17
|
+
- Never call tools with empty parameters; \`domain_indexer_search\` always needs a query, \`get_domain_card\` always needs a DID.
|
|
18
|
+
- Explain what you are searching for, summarize the results, and cite relevant DIDs or entity names.
|
|
19
|
+
- When multiple results appear, compare them briefly and suggest next steps.
|
|
20
|
+
|
|
21
|
+
Task discipline:
|
|
22
|
+
- You are a sub-agent invoked by the main agent. You receive a single task message — that is ALL the context you have.
|
|
23
|
+
- If the task is unclear, ambiguous, or missing critical details (IDs, names, scope, what to do), do NOT guess. Instead, STOP immediately and return a clear message explaining what information you need. The main agent will ask the user and re-invoke you with a complete task.
|
|
24
|
+
- Never loop or retry the same failing approach. If something fails twice, return the error and stop.
|
|
25
|
+
- Complete the requested task and STOP. Do not do additional unrequested work.
|
|
26
|
+
|
|
27
|
+
### Available Domain Indexer Tools
|
|
28
|
+
${formatToolDocs(tools)}
|
|
29
|
+
|
|
30
|
+
Workflow:
|
|
31
|
+
1. Decide if you need search (find relevant entities) or a card lookup (get summary/overview/FAQ for a known DID).
|
|
32
|
+
2. Provide detailed, structured tool inputs (query text, limits, filters, or DID).
|
|
33
|
+
3. Parse the response—highlight summary, overview, FAQ, URLs, and keywords.
|
|
34
|
+
4. Surface gaps or follow-ups — if more context is needed (from memory, portal, etc.), say so and the main agent will use the appropriate tools.
|
|
35
|
+
`.trim();
|
|
36
|
+
|
|
37
|
+
const buildDescription = (tools: PluginTool[]): string => {
|
|
38
|
+
const names = tools.map((t) => t.name).join(', ');
|
|
39
|
+
return `Domain Indexer specialist using (${names}) to discover IXO entities, summaries, overviews, and FAQs.`;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Build the Domain Indexer sub-agent definition. Tools are supplied by the
|
|
44
|
+
* plugin and closed over `DOMAIN_INDEXER_URL` from the merged config.
|
|
45
|
+
*/
|
|
46
|
+
export function createDomainIndexerSubAgent(
|
|
47
|
+
tools: PluginTool[],
|
|
48
|
+
): PluginSubAgent {
|
|
49
|
+
return {
|
|
50
|
+
name: 'Domain Indexer Agent',
|
|
51
|
+
description: buildDescription(tools),
|
|
52
|
+
systemPrompt: buildSystemPrompt(tools),
|
|
53
|
+
tools,
|
|
54
|
+
model: 'subagent',
|
|
55
|
+
middlewares: [],
|
|
56
|
+
};
|
|
57
|
+
}
|