@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,65 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { tool } from '../../../plugin-api/tool-helper';
|
|
3
|
+
import type { PluginSubAgent, PluginTool } from '../../../plugin-api/types';
|
|
4
|
+
import type { Units } from './weather-client';
|
|
5
|
+
import { buildForecastTool, type LastQueryStore } from './weather-tools';
|
|
6
|
+
|
|
7
|
+
function buildRecommendOutfitTool(): PluginTool {
|
|
8
|
+
return tool(
|
|
9
|
+
async (rawArgs) => {
|
|
10
|
+
const { temp_c, conditions } = z
|
|
11
|
+
.object({ temp_c: z.number(), conditions: z.string() })
|
|
12
|
+
.parse(rawArgs);
|
|
13
|
+
const wet = ['rain', 'showers', 'thunderstorm', 'snow'].some((k) =>
|
|
14
|
+
conditions.toLowerCase().includes(k),
|
|
15
|
+
);
|
|
16
|
+
let layer: string;
|
|
17
|
+
if (temp_c >= 25) layer = 'a t-shirt and shorts';
|
|
18
|
+
else if (temp_c >= 18) layer = 'a t-shirt';
|
|
19
|
+
else if (temp_c >= 10) layer = 'a light jacket';
|
|
20
|
+
else if (temp_c >= 0) layer = 'a warm coat and a scarf';
|
|
21
|
+
else layer = 'a heavy winter coat, gloves, and a hat';
|
|
22
|
+
return `Wear ${layer}${wet ? ' and bring an umbrella' : ''}.`;
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'recommend_outfit',
|
|
26
|
+
description:
|
|
27
|
+
'Given a temperature in Celsius and short conditions string, return a one-line outfit suggestion.',
|
|
28
|
+
schema: z.object({
|
|
29
|
+
temp_c: z.number().describe('Temperature in Celsius.'),
|
|
30
|
+
conditions: z.string().describe('e.g. "rain", "clear", "snow".'),
|
|
31
|
+
}),
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const PROMPT = [
|
|
37
|
+
'You are the Weather Planner Agent. You decide whether the user needs a',
|
|
38
|
+
'jacket / umbrella / etc. for a place and time.',
|
|
39
|
+
'',
|
|
40
|
+
'Workflow:',
|
|
41
|
+
'1. Call get_weather_forecast with the city (and days if specified).',
|
|
42
|
+
'2. Pick the most relevant day. If temperatures are Fahrenheit, convert to',
|
|
43
|
+
' Celsius: c = (f - 32) * 5/9.',
|
|
44
|
+
"3. Call recommend_outfit with that day's max temp (Celsius) and conditions.",
|
|
45
|
+
'4. Reply with ONE sentence combining the forecast and the outfit advice,',
|
|
46
|
+
' e.g. "Berlin tomorrow: 14°C with rain — wear a light jacket and bring an umbrella."',
|
|
47
|
+
'',
|
|
48
|
+
'Rules: ALWAYS call the tools. Never invent forecasts. If a tool returns an',
|
|
49
|
+
'error string, return it verbatim.',
|
|
50
|
+
].join('\n');
|
|
51
|
+
|
|
52
|
+
export function buildWeatherPlannerSubAgent(
|
|
53
|
+
defaultUnits: Units,
|
|
54
|
+
store: LastQueryStore,
|
|
55
|
+
): PluginSubAgent {
|
|
56
|
+
return {
|
|
57
|
+
name: 'weather_planner_agent',
|
|
58
|
+
description:
|
|
59
|
+
'Combines a forecast lookup with an outfit recommendation. Use for "should I bring a jacket to X tomorrow?" / "what should I wear in Y?".',
|
|
60
|
+
systemPrompt: PROMPT,
|
|
61
|
+
tools: [buildForecastTool(defaultUnits, store), buildRecommendOutfitTool()],
|
|
62
|
+
model: 'subagent',
|
|
63
|
+
forwardTools: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { tool } from '../../../plugin-api/tool-helper';
|
|
3
|
+
import type { PluginTool, RuntimeContext } from '../../../plugin-api/types';
|
|
4
|
+
import { getCurrentWeather, getForecast, type Units } from './weather-client';
|
|
5
|
+
|
|
6
|
+
export interface LastWeatherQuery {
|
|
7
|
+
city: string;
|
|
8
|
+
latitude: number;
|
|
9
|
+
longitude: number;
|
|
10
|
+
queriedAt: string;
|
|
11
|
+
}
|
|
12
|
+
export type LastQueryStore = Map<string, LastWeatherQuery>;
|
|
13
|
+
|
|
14
|
+
const recordLast = (
|
|
15
|
+
store: LastQueryStore,
|
|
16
|
+
sessionId: string,
|
|
17
|
+
result: { city: string; latitude: number; longitude: number },
|
|
18
|
+
): void => {
|
|
19
|
+
store.set(sessionId, {
|
|
20
|
+
city: result.city,
|
|
21
|
+
latitude: result.latitude,
|
|
22
|
+
longitude: result.longitude,
|
|
23
|
+
queriedAt: new Date().toISOString(),
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Boot-time tool — reads default units from plugin config. */
|
|
28
|
+
export function buildCurrentWeatherTool(
|
|
29
|
+
defaultUnits: Units,
|
|
30
|
+
store: LastQueryStore,
|
|
31
|
+
): PluginTool {
|
|
32
|
+
return tool(
|
|
33
|
+
async (rawArgs, ctx: RuntimeContext) => {
|
|
34
|
+
const { city } = z.object({ city: z.string().min(1) }).parse(rawArgs);
|
|
35
|
+
const result = await getCurrentWeather(
|
|
36
|
+
city,
|
|
37
|
+
defaultUnits,
|
|
38
|
+
ctx.abortSignal,
|
|
39
|
+
);
|
|
40
|
+
if (!result) {
|
|
41
|
+
return `Could not find weather for "${city}". Try a more specific city name.`;
|
|
42
|
+
}
|
|
43
|
+
recordLast(store, ctx.session.id, result);
|
|
44
|
+
return JSON.stringify(result);
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'get_current_weather',
|
|
48
|
+
description:
|
|
49
|
+
'Get the current weather for a city. Returns temperature, wind speed (km/h), conditions, and coordinates.',
|
|
50
|
+
schema: z.object({
|
|
51
|
+
city: z.string().min(1).describe('City name, e.g. "Berlin".'),
|
|
52
|
+
}),
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Per-request tool — reads the user's timezone off `rtCtx`. */
|
|
58
|
+
export function buildForecastTool(
|
|
59
|
+
defaultUnits: Units,
|
|
60
|
+
store: LastQueryStore,
|
|
61
|
+
): PluginTool {
|
|
62
|
+
return tool(
|
|
63
|
+
async (rawArgs, ctx: RuntimeContext) => {
|
|
64
|
+
const { city, days } = z
|
|
65
|
+
.object({
|
|
66
|
+
city: z.string().min(1),
|
|
67
|
+
days: z.number().int().min(1).max(7).optional(),
|
|
68
|
+
})
|
|
69
|
+
.parse(rawArgs);
|
|
70
|
+
const tz =
|
|
71
|
+
ctx.user.timezone && ctx.user.timezone.length > 0
|
|
72
|
+
? ctx.user.timezone
|
|
73
|
+
: 'auto';
|
|
74
|
+
const result = await getForecast(
|
|
75
|
+
city,
|
|
76
|
+
days ?? 3,
|
|
77
|
+
defaultUnits,
|
|
78
|
+
tz,
|
|
79
|
+
ctx.abortSignal,
|
|
80
|
+
);
|
|
81
|
+
if (!result) {
|
|
82
|
+
return `Could not find a forecast for "${city}". Try a more specific city name.`;
|
|
83
|
+
}
|
|
84
|
+
recordLast(store, ctx.session.id, result);
|
|
85
|
+
return JSON.stringify(result);
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'get_weather_forecast',
|
|
89
|
+
description:
|
|
90
|
+
'Get a daily weather forecast for a city (up to 7 days). Returns max/min temp + conditions per day. Uses the user timezone when available, else auto-detects.',
|
|
91
|
+
schema: z.object({
|
|
92
|
+
city: z.string().min(1).describe('City name.'),
|
|
93
|
+
days: z
|
|
94
|
+
.number()
|
|
95
|
+
.int()
|
|
96
|
+
.min(1)
|
|
97
|
+
.max(7)
|
|
98
|
+
.optional()
|
|
99
|
+
.describe('Forecast horizon in days (1-7). Defaults to 3.'),
|
|
100
|
+
}),
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import {
|
|
3
|
+
OraclePlugin,
|
|
4
|
+
type PluginRoute,
|
|
5
|
+
} from '../../../plugin-api/oracle-plugin';
|
|
6
|
+
import type {
|
|
7
|
+
AgentMiddleware,
|
|
8
|
+
AuthExcludedRoute,
|
|
9
|
+
PluginContext,
|
|
10
|
+
PluginManifest,
|
|
11
|
+
PluginSubAgent,
|
|
12
|
+
PluginTool,
|
|
13
|
+
RuntimeContext,
|
|
14
|
+
} from '../../../plugin-api/types';
|
|
15
|
+
import { getCurrentWeather, type Units } from './weather-client';
|
|
16
|
+
import { buildWeatherMiddleware } from './weather-middleware';
|
|
17
|
+
import { buildWeatherPlannerSubAgent } from './weather-sub-agent';
|
|
18
|
+
import {
|
|
19
|
+
buildCurrentWeatherTool,
|
|
20
|
+
buildForecastTool,
|
|
21
|
+
type LastQueryStore,
|
|
22
|
+
type LastWeatherQuery,
|
|
23
|
+
} from './weather-tools';
|
|
24
|
+
|
|
25
|
+
const NAME = 'weather';
|
|
26
|
+
const VERSION = '0.1.0';
|
|
27
|
+
|
|
28
|
+
const configSchema = z.object({
|
|
29
|
+
WEATHER_DEFAULT_UNITS: z.enum(['celsius', 'fahrenheit']).default('celsius'),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const manifest: PluginManifest = {
|
|
33
|
+
title: 'Weather',
|
|
34
|
+
summary:
|
|
35
|
+
'ALWAYS use the provided weather tools to answer user questions about current weather, forecasts, or outfit recommendations. Do NOT attempt to answer from prior knowledge or assumptions—call the appropriate tool for every weather-related request to obtain up-to-date results. Powered by Open-Meteo (no API key required).',
|
|
36
|
+
whenToUse: [
|
|
37
|
+
'Whenever asked about current weather, temperature, precipitation, wind, or other weather conditions in any city or region.',
|
|
38
|
+
'Any question related to forecasts (e.g., today, tomorrow, next week, weekend, specific date) for any location.',
|
|
39
|
+
'Whenever the user asks "what should I wear", "do I need an umbrella", "do I need a jacket", or similar outfit/clothing guidance—ALWAYS use the weather tools to get real forecast data first.',
|
|
40
|
+
'If the user inquires about whether to bring weather-related items (umbrella, jacket, sunglasses, etc.), make sure to call the relevant weather tool(s) before providing recommendations.',
|
|
41
|
+
],
|
|
42
|
+
whenNotToUse: [
|
|
43
|
+
'Questions about historical or long-term climate data (the tools provide only current or short-term forecast information).',
|
|
44
|
+
'Locations smaller than city-level precision (e.g., a specific street address)—weather will be provided for the nearest city centroid.',
|
|
45
|
+
'If the user asks about unrelated topics not connected to weather, temperature, climate, or outfit recommendations.',
|
|
46
|
+
],
|
|
47
|
+
examples: [
|
|
48
|
+
{
|
|
49
|
+
user: "What's the weather in Berlin?",
|
|
50
|
+
tool: 'get_current_weather',
|
|
51
|
+
args: { city: 'Berlin' },
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
user: 'Forecast for Tokyo this week.',
|
|
55
|
+
tool: 'get_weather_forecast',
|
|
56
|
+
args: { city: 'Tokyo', days: 7 },
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
user: 'Should I bring a jacket to Berlin tomorrow?',
|
|
60
|
+
tool: 'call_weather_planner_agent',
|
|
61
|
+
args: {
|
|
62
|
+
task: 'Decide whether the user needs a jacket in Berlin tomorrow. Always fetch a 2-day weather forecast for Berlin, select the tomorrow value, and then recommend an outfit. Do NOT answer without calling get_weather_forecast first.',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
user: 'Do I need an umbrella in Paris today?',
|
|
67
|
+
tool: 'get_current_weather',
|
|
68
|
+
args: { city: 'Paris' },
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
user: 'Is it cold in San Francisco this weekend?',
|
|
72
|
+
tool: 'get_weather_forecast',
|
|
73
|
+
args: { city: 'San Francisco', days: 3 },
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
tags: [
|
|
77
|
+
'weather',
|
|
78
|
+
'forecast',
|
|
79
|
+
'outfit',
|
|
80
|
+
'travel',
|
|
81
|
+
'temperature',
|
|
82
|
+
'rain',
|
|
83
|
+
'umbrella',
|
|
84
|
+
'jacket',
|
|
85
|
+
'clothing',
|
|
86
|
+
'recommendation',
|
|
87
|
+
],
|
|
88
|
+
category: 'data',
|
|
89
|
+
visibility: 'on-demand',
|
|
90
|
+
stability: 'experimental',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
interface WeatherNowResponse {
|
|
94
|
+
ok: boolean;
|
|
95
|
+
city?: string;
|
|
96
|
+
temp_c?: number;
|
|
97
|
+
units?: Units;
|
|
98
|
+
conditions?: string;
|
|
99
|
+
latitude?: number;
|
|
100
|
+
longitude?: number;
|
|
101
|
+
error?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Weather plugin — exercises every documented `OraclePlugin` hook:
|
|
106
|
+
* • `getTools` → `get_current_weather` (boot-time, uses config)
|
|
107
|
+
* • `getRequestTools` → `get_weather_forecast` (reads `rtCtx.user.timezone`)
|
|
108
|
+
* • `getSubAgents` → Weather Planner Agent (forecast → outfit chain)
|
|
109
|
+
* • `getMiddlewares` → logs before/after every model call w/ elapsed ms
|
|
110
|
+
* • `getRoutes` → `GET /weather/now?city=X` (Open-Meteo via fetch)
|
|
111
|
+
* • `getAuthExcludedRoutes` → opts `/weather/now` out of UCAN auth
|
|
112
|
+
* • `getSharedState` → `lastWeatherQuery` accessor for other plugins
|
|
113
|
+
* • `configSchema` → optional `WEATHER_DEFAULT_UNITS` (celsius|fahrenheit)
|
|
114
|
+
* • `autoDetect` → always-on (no env gate)
|
|
115
|
+
* • `manifest.visibility` → `on-demand` so `load_capability` is testable
|
|
116
|
+
*
|
|
117
|
+
* Port of `apps/qiforge-example/src/plugins/weather` — identical except the
|
|
118
|
+
* NestJS controller became a `getRoutes()` entry.
|
|
119
|
+
*/
|
|
120
|
+
export class WeatherPlugin extends OraclePlugin {
|
|
121
|
+
static readonly NAME = NAME;
|
|
122
|
+
|
|
123
|
+
readonly name = NAME;
|
|
124
|
+
|
|
125
|
+
readonly version = VERSION;
|
|
126
|
+
|
|
127
|
+
readonly manifest = manifest;
|
|
128
|
+
|
|
129
|
+
override readonly configSchema = configSchema;
|
|
130
|
+
|
|
131
|
+
override readonly autoDetectHint =
|
|
132
|
+
'always on (set WEATHER_DEFAULT_UNITS to celsius|fahrenheit)';
|
|
133
|
+
|
|
134
|
+
/** Most recent query per session — written by tools, read via `getSharedState`. */
|
|
135
|
+
private readonly lastBySession: LastQueryStore = new Map<
|
|
136
|
+
string,
|
|
137
|
+
LastWeatherQuery
|
|
138
|
+
>();
|
|
139
|
+
|
|
140
|
+
override autoDetect(): boolean {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private units(config: unknown): Units {
|
|
145
|
+
return configSchema.parse(config).WEATHER_DEFAULT_UNITS;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
override getTools(ctx: PluginContext): PluginTool[] {
|
|
149
|
+
return [
|
|
150
|
+
buildCurrentWeatherTool(this.units(ctx.config), this.lastBySession),
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
override getRequestTools(rtCtx: RuntimeContext): PluginTool[] {
|
|
155
|
+
return [buildForecastTool(this.units(rtCtx.config), this.lastBySession)];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
override getSubAgents(ctx: PluginContext): PluginSubAgent[] {
|
|
159
|
+
return [
|
|
160
|
+
buildWeatherPlannerSubAgent(this.units(ctx.config), this.lastBySession),
|
|
161
|
+
];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
override getMiddlewares(ctx: PluginContext): AgentMiddleware[] {
|
|
165
|
+
return [buildWeatherMiddleware(ctx)];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
override getRoutes(ctx: PluginContext): PluginRoute[] {
|
|
169
|
+
const units = this.units(ctx.config);
|
|
170
|
+
return [
|
|
171
|
+
{
|
|
172
|
+
method: 'GET',
|
|
173
|
+
path: '/weather/now',
|
|
174
|
+
handler: async (request) => {
|
|
175
|
+
const city = new URL(request.url).searchParams.get('city');
|
|
176
|
+
const body = await weatherNow(city, units);
|
|
177
|
+
return Response.json(body, { status: body.ok ? 200 : 400 });
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
override getAuthExcludedRoutes(): AuthExcludedRoute[] {
|
|
184
|
+
// `/weather/now` is a public lookup — Open-Meteo doesn't need user auth.
|
|
185
|
+
return [{ path: 'weather/now', method: 'GET' }];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
override getSharedState(): Record<
|
|
189
|
+
string,
|
|
190
|
+
(state: unknown, runCtx: RuntimeContext) => unknown
|
|
191
|
+
> {
|
|
192
|
+
return {
|
|
193
|
+
lastWeatherQuery: (_state, runCtx) =>
|
|
194
|
+
this.lastBySession.get(runCtx.session.id),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** `GET /weather/now?city=X` body — same shape as the Node controller's. */
|
|
200
|
+
export async function weatherNow(
|
|
201
|
+
city: string | null,
|
|
202
|
+
units: Units,
|
|
203
|
+
): Promise<WeatherNowResponse> {
|
|
204
|
+
if (!city || city.trim().length === 0) {
|
|
205
|
+
return { ok: false, error: 'Missing required query param: city' };
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const result = await getCurrentWeather(city, units);
|
|
209
|
+
if (!result) {
|
|
210
|
+
return { ok: false, error: `Could not find weather for "${city}".` };
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
ok: true,
|
|
214
|
+
city: result.city,
|
|
215
|
+
temp_c: result.temp,
|
|
216
|
+
units: result.units,
|
|
217
|
+
conditions: result.conditions,
|
|
218
|
+
latitude: result.latitude,
|
|
219
|
+
longitude: result.longitude,
|
|
220
|
+
};
|
|
221
|
+
} catch (err) {
|
|
222
|
+
return {
|
|
223
|
+
ok: false,
|
|
224
|
+
error: err instanceof Error ? err.message : 'Weather lookup failed.',
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|