What: Separate UI-mode (OpenAI) and normal-mode tool behavior into independent, self-contained modules with a shared core logic layer and a mode-aware Actor Executor pattern.
Why:
if (uiMode === 'openai')is scattered across 8+ files with substantial behavioral differences (sync vs async execution, different schemas, different response formats, widget metadata)- Direct actor tools (
type: 'actor') are completely UI-mode-unaware — they always run synchronously without widgets, even in OpenAI mode - The tools-loader uses a fragile deep-clone hack (JSON.parse/stringify with function reattachment) to customize tool descriptions per mode
- Two tools (
search-actors-internal,fetch-actor-details-internal) already use separate definitions, but three others (call-actor,search-actors,get-actor-run) use inline branching — inconsistent patterns
Impact:
- Clean separation of concerns — each mode variant is self-contained
- Direct actor tools become mode-aware (currently broken in UI mode)
- Adding a new UI mode (e.g.,
'anthropic') becomes additive, not invasive - Eliminates the deep-clone hack in tools-loader
- Deduplicates actor dispatch logic in server.ts (main handler + task handler)
- Fixes Skyfire schema mutation safety (tool definitions become immutable via
Object.freeze)
Effort: 6-10 developer days
Risk: Medium — requires coordination with apify-mcp-server-internal, no public API name changes
Explicit decisions made during planning, for future reference.
| Decision | Choice | Rationale |
|---|---|---|
actor-mcp proxy tools |
Passthrough only, no mode awareness | Proxy tools forward to external MCP servers; we don't control their response format. No widget wrapping. |
add-actor tool |
Leave as-is, do not make mode-aware | Likely to be deprecated. Not worth the investment. It stays in common/. |
| Skyfire schema mutation | Object.freeze tool definitions; apply Skyfire fields at build time during server init |
Skyfire mutation of shared tool objects has caused production bugs. Tool definitions must be immutable. Skyfire skyfire-pay-id injection happens once when the server builds its tool set, producing new frozen objects. |
| Task lifecycle semantics | "Task completed" = tool handler returned | For async actor starts, "completed" means the call function finished (i.e., Actor was started), not that the Actor run finished. This matches current behavior. Async task rework is planned separately. |
| Phases 2+3 shipping | Ship together as a single PR | Executor wiring (Phase 2) changes runtime behavior for direct actor tools. Shipping without the tool split (Phase 3) would leave call-actor branching inline while direct actors use the executor — an inconsistent intermediate state. |
| Tool names and categories | No renames. External API unchanged. | ToolCategory type, tool names, and tools input parameter remain identical. 'actors' selector resolves to mode-correct tools internally. get-actor-run stays inside the runs category (no new runs_status category). |
openai/ _meta stripping |
Retain getToolPublicFieldOnly filter |
Cheap defense-in-depth. Even after separation, a future regression could leak openai _meta into a non-openai response. The filter is a few lines and prevents catastrophic leakage. |
server.ts → CallToolRequestSchema handler
├── tool.type === 'internal' → tool.call(args) ← call-actor, search-actors, etc.
├── tool.type === 'actor-mcp' → connectMCPClient(...) ← MCP proxy tools (passthrough, unchanged)
└── tool.type === 'actor' → callActorGetDataset(...) ← direct actor tools (e.g., apify/rag-web-browser)
Critical gap: The type: 'actor' dispatch path (lines 851-892 in server.ts) has zero UI-mode awareness. callActorGetDataset() always runs synchronously, and buildActorResponseContent() never attaches widget metadata. This means direct actor tools behave identically regardless of uiMode.
A second copy of this dispatch logic exists in executeToolAndUpdateTask() (lines 1056-1089), acknowledged with a TODO about duplication.
| File | What Changes | Behavioral Difference |
|---|---|---|
tools/actor.ts (call-actor) |
Execution mode, response text, _meta |
Forced async + widget vs sync + full results |
tools/store_collection.ts (search-actors) |
Response format, _meta, widget actors |
Widget cards vs text cards |
tools/fetch-actor-details.ts |
Response content, _meta, output schema fetch |
Widget response vs full text + output schema |
tools/run.ts (get-actor-run) |
Response text, _meta |
Abbreviated + widget vs full JSON dump |
utils/tools-loader.ts |
Tool selection, description mutation, deep clone | Adds UI tools, mutates call-actor description |
utils/server-instructions.ts |
Server instruction text | Entirely different workflow rules |
utils/tools.ts |
_meta field stripping |
Strips openai/ prefixed keys in non-openai mode |
mcp/server.ts |
Tool listing, widget resolution | Widget resource resolution gated on mode |
resources/resource_service.ts |
Resource listing | Widget HTML resources only in openai mode |
Two tools already have separate UI variants:
search-actors-internal(openaiOnly: true) — lightweight search for LLM token savingsfetch-actor-details-internal(openaiOnly: true) — lightweight details without widget rendering
But call-actor, search-actors, fetch-actor-details, and get-actor-run use inline if/else branching instead.
┌─────────────────────────────────────────────────┐
│ Layer 3: Mode-Specific Tool Definitions │
│ (description, outputSchema, _meta, response │
│ formatting, execution semantics) │
│ │
│ default/call-actor.ts openai/call-actor.ts │
│ default/search-actors openai/search-actors │
│ default/get-actor-run openai/get-actor-run │
│ default/actor-executor openai/actor-executor │
├─────────────────────────────────────────────────┤
│ Layer 2: Mode Registry + Loader │
│ (selects correct tool set at startup, │
│ no runtime branching, no deep cloning) │
├─────────────────────────────────────────────────┤
│ Layer 1: Shared Core Logic │
│ (actor resolution, API calls, input │
│ validation, dataset fetching, schema gen) │
│ │
│ core/actor-execution.ts │
│ core/actor-search.ts │
│ core/actor-details.ts │
└─────────────────────────────────────────────────┘
The Actor Executor solves the critical gap where direct actor tools (type: 'actor') are UI-mode-unaware. It also eliminates the duplicated dispatch logic between the main handler and the task handler.
Current flow (server.ts, for type: 'actor'):
// SAME behavior regardless of uiMode — always sync, no widget
const callResult = await callActorGetDataset({ ... });
const { content, structuredContent } = buildActorResponseContent(actorName, callResult);
return { content, structuredContent };Target flow (server.ts, for type: 'actor'):
// Mode-aware execution via ActorExecutor
const executor = this.actorExecutor; // Set at construction based on uiMode
return executor.executeActorTool({
actorName: tool.actorFullName,
input: actorArgs,
apifyClient,
callOptions: { memory: tool.memoryMbytes },
progressTracker,
abortSignal: extra.signal,
mcpSessionId,
});ActorExecutor interface:
type ActorExecutor = {
/** Execute a direct actor tool (type: 'actor') */
executeActorTool(params: ActorExecutionParams): Promise<ToolResponse>;
};| Mode | Executor | Behavior |
|---|---|---|
| default | DefaultActorExecutor |
Sync: callActorGetDataset() → buildActorResponseContent() (same as today) |
| openai | OpenAIActorExecutor |
Async: actorClient.start() → return runId + widget _meta + abbreviated text |
The same executor is used by both the main handler and the task handler, eliminating the dispatch duplication.
actor-mcp tools are explicitly out of scope: They are passthrough proxies to external MCP servers. Their response format is controlled by the remote server, not by us. No executor wrapping.
Problem: Skyfire mode mutates tool schemas at runtime (adds skyfire-pay-id property, appends description text). This has caused production bugs where shared tool objects were corrupted across modes/sessions.
Solution: All tool definitions are Object.freeze()-d after construction. Skyfire augmentation produces new frozen objects at server init time rather than mutating existing ones.
// At server initialization (upsertTools or equivalent)
function buildToolForRegistration(tool: ToolEntry, skyfireMode: boolean): ToolEntry {
if (!skyfireMode || !shouldModifyForSkyfire(tool)) {
return Object.freeze(tool);
}
// Create new object with Skyfire fields baked in
return Object.freeze({
...tool,
description: `${tool.description}\n\n${SKYFIRE_TOOL_INSTRUCTIONS}`,
inputSchema: addSkyfirePayIdProperty(tool.inputSchema),
});
}src/tools/
├── core/ # Layer 1: Shared business logic
│ ├── actor-execution.ts # callActorGetDataset(), startActorAsync(), resolveActor()
│ ├── actor-search.ts # searchApifyStore(), formatActorCard()
│ ├── actor-details.ts # fetchActorDetails(), processActorDetailsForResponse()
│ └── actor-response.ts # buildActorResponseContent() (moved from utils/)
│
├── default/ # Layer 3: Normal mode tool definitions
│ ├── call-actor.ts # Sync execution, full results, text response
│ ├── search-actors.ts # Text-based actor cards, no widget
│ ├── fetch-actor-details.ts # Full details + output schema fetch
│ ├── get-actor-run.ts # Full JSON run dump
│ └── actor-executor.ts # DefaultActorExecutor: sync + plain response
│
├── openai/ # Layer 3: OpenAI UI mode tool definitions
│ ├── call-actor.ts # Forced async, widget metadata, abbreviated text
│ ├── search-actors.ts # Widget actors, interactive card format
│ ├── fetch-actor-details.ts # Simplified structured content + widget config
│ ├── get-actor-run.ts # Abbreviated text + widget metadata
│ ├── search-actors-internal.ts # Moved from tools/ (already exists)
│ ├── fetch-actor-details-internal.ts # Moved from tools/ (already exists)
│ └── actor-executor.ts # OpenAIActorExecutor: async + widget response
│
├── common/ # Tools identical across all modes
│ ├── get-actor-output.ts
│ ├── dataset.ts
│ ├── dataset_collection.ts
│ ├── key_value_store.ts
│ ├── key_value_store_collection.ts
│ ├── run_collection.ts
│ ├── run.ts # abort-actor-run, get-actor-log (mode-independent)
│ ├── search-apify-docs.ts
│ ├── fetch-apify-docs.ts
│ ├── get-html-skeleton.ts
│ └── helpers.ts # add-actor tool (unchanged, stays here)
│
├── categories.ts # Mode-aware category registry
└── index.ts # Re-exports
Important: No new category names. The existing ToolCategory type and external tools input parameter remain unchanged. Internally, the registry resolves mode-correct tool implementations behind the same category names.
// categories.ts
import { defaultSearchActors, defaultFetchActorDetails, defaultCallActor } from './default/index.js';
import { defaultGetActorRun } from './default/get-actor-run.js';
import { openaiSearchActors, openaiFetchActorDetails, openaiCallActor } from './openai/index.js';
import { openaiGetActorRun } from './openai/get-actor-run.js';
import { searchActorsInternal, fetchActorDetailsInternal } from './openai/index.js';
// ... common tool imports ...
/** Build the tool categories for a given mode. Same category names, different implementations. */
function buildCategories(uiMode?: UiMode) {
const isOpenai = uiMode === 'openai';
return {
actors: isOpenai
? [openaiSearchActors, openaiFetchActorDetails, openaiCallActor]
: [defaultSearchActors, defaultFetchActorDetails, defaultCallActor],
runs: [
isOpenai ? openaiGetActorRun : defaultGetActorRun,
getUserRunsList,
getActorRunLog,
abortActorRun,
],
// openai-only tools injected alongside actors when in openai mode
...(isOpenai && {
ui: [searchActorsInternal, fetchActorDetailsInternal],
}),
docs: [searchApifyDocsTool, fetchApifyDocsTool],
storage: [getDataset, getDatasetItems, getDatasetSchema, getActorOutput, ...],
experimental: [addTool],
dev: [getHtmlSkeleton],
};
}// tools-loader.ts (simplified — no deep cloning, no description mutation, no openai filtering)
export async function loadToolsFromInput(input: Input, apifyClient: ApifyClient, uiMode?: UiMode): Promise<ToolEntry[]> {
// 1. Build mode-resolved categories (already has correct tools for this mode)
const categories = buildCategories(uiMode);
// 2. Select tools based on input.tools selectors (same logic as today)
const result = resolveSelectorsToTools(input, categories);
// 3. In openai mode, add UI-specific tools
if (uiMode === 'openai' && categories.ui) {
result.push(...categories.ui);
}
// 4. Load actor tools (if any)
if (actorNamesToLoad.length > 0) {
result.push(...await getActorsAsTools(actorNamesToLoad, apifyClient));
}
// 5. Auto-inject companion tools (get-actor-run, get-actor-output)
injectCompanionTools(result, categories);
// 6. Deduplicate (no deep-clone, no filtering, no description mutation)
return deduplicateByName(result);
}What the loader no longer does:
- Deep-clone tools via JSON.parse/stringify and reattach functions
- Mutate
call-actordescription based on mode - Filter out
openaiOnlytools (they're only in the openai category build)
What the loader still does (unchanged):
- Selector resolution (
input.tools→ category names / tool names / actor names) - Actor tool loading
- Companion tool auto-injection
- Deduplication
// server.ts constructor
class ActorsMcpServer {
private actorExecutor: ActorExecutor;
constructor(options: ActorsMcpServerOptions) {
// Select executor based on mode (once, at construction time)
this.actorExecutor = options.uiMode === 'openai'
? new OpenAIActorExecutor()
: new DefaultActorExecutor();
// Server instructions also selected once
this.serverInstructions = getServerInstructions(options.uiMode);
}
}
// In setupToolHandlers() — actor dispatch becomes one line:
if (tool.type === 'actor') {
return this.actorExecutor.executeActorTool({ ... });
}
// In executeToolAndUpdateTask() — same one line, eliminating duplication:
if (tool.type === 'actor') {
return this.actorExecutor.executeActorTool({ ... });
}
// actor-mcp dispatch UNCHANGED — passthrough only:
if (tool.type === 'actor-mcp') {
// ... existing connectMCPClient() logic, no mode awareness ...
}src/utils/
├── server-instructions/
│ ├── common.ts # Shared instruction text
│ ├── default.ts # Normal mode instructions
│ └── openai.ts # UI mode instructions (widget workflow rules)
// server-instructions/index.ts
export function getServerInstructions(uiMode?: UiMode): string {
const common = getCommonInstructions();
const modeSpecific = uiMode === 'openai' ? getOpenAIInstructions() : getDefaultInstructions();
return `${common}\n\n${modeSpecific}`;
}Decision: Same external tool names across modes. One mode is active per server instance.
| Tool | Name in default mode | Name in openai mode | Notes |
|---|---|---|---|
| call-actor | call-actor |
call-actor |
Different implementation, same name |
| search-actors | search-actors |
search-actors |
Different response format |
| fetch-actor-details | fetch-actor-details |
fetch-actor-details |
Different content |
| get-actor-run | get-actor-run |
get-actor-run |
Different response format |
| search-actors-internal | N/A | search-actors-internal |
openai-only |
| fetch-actor-details-internal | N/A | fetch-actor-details-internal |
openai-only |
Why same names: Clients calling call-actor keep working. Mode is a server-level config, not a per-tool concept. No discovery ambiguity.
Category names are unchanged: actors, runs, docs, storage, experimental, dev. The tools input parameter accepts the same values as today.
This is the critical gap the Actor Executor pattern fixes.
| Aspect | Today (broken) | After refactor |
|---|---|---|
| Execution mode | Always sync | Mode-aware (sync default, async openai) |
| Response format | Always plain text | Mode-aware (plain text vs widget) |
| Widget metadata | Never attached | Attached in openai mode |
| Dispatch location | Hardcoded in server.ts (2 places) | Single actorExecutor.executeActorTool() |
| Consistency with call-actor | Inconsistent in UI mode | Same behavior regardless of dispatch path |
Each phase becomes a PR. PRs are chained: each targets the previous feature branch, not main. This allows incremental review while keeping main stable until the full feature is ready.
The tool split phase (Phase 3) is broken into one PR per tool to keep each PR small (~200-350 lines) and easy to review. Each tool split PR creates the default + openai variants and converts the original file to an adapter. The adapter preserves existing exports so categories.ts stays unchanged until the registry PR.
main
└── feat/tool-mode-separation-plan ← PR #1: plan document (this file)
└── feat/tool-mode-core-extraction ← PR #2: Phase 1 (shared core logic)
└── feat/tool-mode-executor ← PR #3a: Phase 2 (Actor Executor pattern)
└── feat/tool-mode-tool-split ← PR #3b: Phase 3 prep (plan update)
└── feat/split-fetch-actor-details ← PR #3c: Split fetch-actor-details
└── feat/split-search-actors ← PR #3d: Split search-actors
└── feat/split-get-actor-run ← PR #3e: Split get-actor-run
└── feat/split-call-actor ← PR #3f: Split call-actor
└── ... (future: move, freeze, registry, tests)
Merge order: PR #1 → #2 → #3a → #3b → #3c → #3d → #3e → #3f → ..., each into its parent. Final merge of the base branch into main.
Review strategy: Each PR is independently reviewable. Reviewer can check that tests pass at each level. The plan PR (#1) provides context for all subsequent PRs.
Branch: feat/tool-mode-separation-plan (from main)
Contents: This plan document (res/tool-mode-separation-plan.md) + index update.
Review focus: Architecture approval before any code changes.
Branch: feat/tool-mode-core-extraction (from feat/tool-mode-separation-plan)
Goal: Move business logic out of tool handlers into mode-agnostic core modules. Pure refactor, no behavioral changes.
Changes:
-
Create
src/tools/core/actor-execution.ts:- Move
callActorGetDataset()fromsrc/tools/actor.ts - Move
startActorAsync()logic (currently inline in call-actor's async branch) - Move actor resolution (
getActorsAsToolslookup, MCP URL check)
- Move
-
Create
src/tools/core/actor-search.ts:- Move
searchApifyStoreActors()call logic - Move
formatActorToActorCard(),formatActorToStructuredCard(),formatActorForWidget()
- Move
-
Create
src/tools/core/actor-details.ts:- Move
fetchActorDetails()call logic - Move
processActorDetailsForResponse(),buildActorDetailsTextResponse()
- Move
-
Move
src/utils/actor-response.ts→src/tools/core/actor-response.ts -
Update all imports in existing tool files to point at new core modules
Verification: npm run type-check && npm run lint && npm run test:unit — all pass. No behavioral changes.
Review focus: Are the extraction boundaries clean? Does the core layer have zero presentation/mode concerns?
Branch: feat/tool-mode-executor (from feat/tool-mode-core-extraction)
Goal: Implement the Actor Executor pattern. Fixes the gap where direct actor tools are mode-unaware.
Changes:
-
Define
ActorExecutortype insrc/types.ts:type ActorExecutor = { executeActorTool(params: ActorExecutionParams): Promise<ToolResponse>; };
-
Implement
DefaultActorExecutorinsrc/tools/default/actor-executor.ts:- Uses
callActorGetDataset()(sync) - Uses
buildActorResponseContent()(plain text)
- Uses
-
Implement
OpenAIActorExecutorinsrc/tools/openai/actor-executor.ts:- Uses
actorClient.start()(async) - Returns widget metadata + abbreviated text
- Uses
-
Add
actorExecutorfield toActorsMcpServer, set in constructor based onuiMode -
Replace both dispatch paths in
server.ts:setupToolHandlers()→this.actorExecutor.executeActorTool()executeToolAndUpdateTask()→this.actorExecutor.executeActorTool()
Verification: npm run type-check && npm run lint && npm run test:unit — all pass.
Review focus: Does the executor interface cover all dispatch needs? Are the executors correctly wired in server.ts?
Branch: feat/tool-mode-tool-split (from feat/tool-mode-executor)
Goal: Update this plan document to reflect the granular per-tool split strategy.
Changes: This plan document only.
Branch: feat/split-fetch-actor-details (from feat/tool-mode-tool-split)
Goal: Split fetch-actor-details into default/openai variants with an adapter.
Changes:
- Create
src/tools/default/fetch-actor-details.ts— full text + output schema fetch - Create
src/tools/openai/fetch-actor-details.ts— simplified structured content + widget_meta - Convert
src/tools/fetch-actor-details.tsto adapter (dispatches based onuiMode)
The adapter preserves the existing fetchActorDetailsTool export so categories.ts is unchanged.
Verification: npm run type-check && npm run lint && npm run test:unit
Branch: feat/split-search-actors (from feat/split-fetch-actor-details)
Goal: Split search-actors into default/openai variants with an adapter.
Changes:
- Create
src/tools/default/search-actors.ts— text actor cards, no widget - Create
src/tools/openai/search-actors.ts— widget actors +_meta+ interactive card text - Convert
src/tools/store_collection.tsto adapter (dispatches based onuiMode)
The adapter preserves the existing searchActors and searchActorsArgsSchema exports.
Verification: npm run type-check && npm run lint && npm run test:unit
Branch: feat/split-get-actor-run (from feat/split-search-actors)
Goal: Split get-actor-run into default/openai variants with an adapter.
Changes:
- Create
src/tools/default/get-actor-run.ts— full JSON run dump - Create
src/tools/openai/get-actor-run.ts— abbreviated text + widget_meta - Convert the
getActorRunexport insrc/tools/run.tsto an adapter
Mode-independent tools in run.ts (getActorRunLog, abortActorRun) are untouched.
Verification: npm run type-check && npm run lint && npm run test:unit
Branch: feat/split-call-actor (from feat/split-get-actor-run)
Goal: Split call-actor into default/openai variants with an adapter.
Changes:
- Create
src/tools/default/call-actor.ts— sync execution, referencessearch-actors/fetch-actor-details - Create
src/tools/openai/call-actor.ts— forced async, widget_meta, references*-internaltools - Convert
src/tools/actor.tsto adapter (dispatches based onuiMode)
Each variant has its own description (no runtime mutation needed). The adapter preserves
existing exports (callActor, getCallActorDescription, callActorGetDataset, getActorsAsTools).
Verification: npm run type-check && npm run lint && npm run test:unit
The following phases will be planned in detail once the tool splits are merged:
- Move & freeze: Move tools to
common//openai/directories,Object.freezeall definitions - Registry + loader cleanup:
buildCategories(uiMode), remove deep-clone hack, removeopenaiOnly - Server instructions split: Split into
common.ts,default.ts,openai.ts - Contract tests: Mode-parameterized tests + cross-repo coordination
| Risk | Severity | Mitigation |
|---|---|---|
Breaking apify-mcp-server-internal |
High | Use pkg.pr.new preview packages. Add contract tests for tool list + schemas. |
Public API break (tools input parameter) |
High | Category names and tool names unchanged. 'actors' resolves to mode-correct tools. |
| Skyfire schema mutation corrupting shared objects | High | Object.freeze all tool definitions. Skyfire augmentation at build time produces new objects. |
| Circular dependencies during extraction | Medium | Enforce direction: core → utils/types; mode tools → core; never reverse. |
| Shared logic drift between mode variants | Medium | Core layer owns all business logic. Mode tools only format responses. |
_meta leakage across modes |
Low | Retain getToolPublicFieldOnly openai meta stripping as defense-in-depth. |
| Testing surface expansion | Low | Parameterized test suite runs same assertions per mode. |
- All existing unit and integration tests pass
- Direct actor tools are mode-aware (async + widget in openai, sync in default)
- No
if (uiMode === 'openai')in tool handlers (moved to tool selection) - Deep-clone hack in tools-loader eliminated
- Actor dispatch duplication in server.ts eliminated
- All tool definitions are
Object.freeze()-d (Skyfire safety) -
openaiOnlyfield removed fromToolBasetype -
actor-mcpproxy tools unchanged (passthrough only) -
add-actortool unchanged (stays incommon/) - Tool names and category names unchanged (external API identical)
-
getToolPublicFieldOnly_metafilter retained -
apify-mcp-server-internalworks without breaking changes - Adding a hypothetical
'anthropic'mode requires only new files insrc/tools/anthropic/
| File | Purpose | PR |
|---|---|---|
src/tools/core/actor-execution.ts |
Shared actor execution logic | #2 |
src/tools/core/actor-search.ts |
Shared store search logic | #2 |
src/tools/core/actor-details.ts |
Shared actor details logic | #2 |
src/tools/core/actor-response.ts |
Shared response builder (moved from utils/) | #2 |
src/tools/default/actor-executor.ts |
DefaultActorExecutor | #3a |
src/tools/openai/actor-executor.ts |
OpenAIActorExecutor | #3a |
src/tools/default/fetch-actor-details.ts |
Normal mode fetch-actor-details | #3c |
src/tools/openai/fetch-actor-details.ts |
OpenAI mode fetch-actor-details | #3c |
src/tools/default/search-actors.ts |
Normal mode search-actors | #3d |
src/tools/openai/search-actors.ts |
OpenAI mode search-actors | #3d |
src/tools/default/get-actor-run.ts |
Normal mode get-actor-run | #3e |
src/tools/openai/get-actor-run.ts |
OpenAI mode get-actor-run | #3e |
src/tools/default/call-actor.ts |
Normal mode call-actor | #3f |
src/tools/openai/call-actor.ts |
OpenAI mode call-actor | #3f |
| File | Changes | PR |
|---|---|---|
src/tools/actor.ts |
Extract core logic to core/ modules |
#2; convert to adapter |
src/tools/store_collection.ts |
Extract core logic to core/ modules |
#2; convert to adapter |
src/tools/fetch-actor-details.ts |
Extract core logic to core/ modules |
#2; convert to adapter |
src/tools/run.ts |
Extract core logic to core/ modules |
#2; convert getActorRun to adapter |
src/mcp/server.ts |
Add actorExecutor field; replace 2 actor dispatch blocks |
#3a |
src/types.ts |
Add ActorExecutor type |
#3a |
| From | To | PR |
|---|---|---|
src/utils/actor-response.ts |
src/tools/core/actor-response.ts |
#2 |
| File | Reason |
|---|---|
src/tools/helpers.ts (add-actor) |
Not mode-aware; potential future deprecation |
src/mcp/proxy.ts (actor-mcp) |
Passthrough only; no mode awareness needed |
src/utils/tools.ts (getToolPublicFieldOnly) |
_meta stripping retained as defense-in-depth |