-
Notifications
You must be signed in to change notification settings - Fork 217
Expand file tree
/
Copy pathtools.ts
More file actions
174 lines (151 loc) · 6.31 KB
/
Copy pathtools.ts
File metadata and controls
174 lines (151 loc) · 6.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import { type HelperTools } from '../const.js';
import {
SKYFIRE_ENABLED_TOOLS,
SKYFIRE_PAY_ID_PROPERTY_DESCRIPTION,
SKYFIRE_TOOL_INSTRUCTIONS,
} from '../payments/const.js';
import type { CallDiagnostics, HelperTool, ToolBase, ToolEntry, ToolInputSchema } from '../types.js';
import { ServerMode } from '../types.js';
import { fixZodSchemaRequired } from './ajv.js';
/**
* Returns the canonical full name for a tool.
* For actor tools this is actorFullName (e.g. "apify/rag-web-browser"),
* for all others it's the tool name.
*/
export function getToolFullName(tool: ToolEntry): string {
switch (tool.type) {
case 'actor': return tool.actorFullName;
case 'internal':
case 'actor-mcp': return tool.name;
default: return (tool satisfies never as ToolEntry).name;
}
}
/**
* Extract stable Actor ID for telemetry.
* Available for actor and actor-mcp tools; undefined for internal tools.
*/
export function extractActorId(tool: ToolEntry): string | undefined {
if (tool.type === 'actor' || tool.type === 'actor-mcp') return tool.actorId;
return undefined;
}
/**
* Build actor identification fields for failure telemetry.
*/
export function buildActorFields(actorName?: string, actorId?: string): Pick<CallDiagnostics, 'actor_name' | 'actor_id'> {
return {
...(actorName ? { actor_name: actorName } : {}),
...(actorId ? { actor_id: actorId } : {}),
};
}
/**
* Extract actor name for telemetry from the tool entry or call-actor args.
* For actor tools, read from the tool entry. For call-actor, parse from the `actor` arg.
* Returns undefined for other internal tools or when the arg is missing/invalid.
*/
export function extractActorName(tool: ToolEntry, args?: Record<string, unknown>): string | undefined {
if (tool.type === 'actor') return tool.actorFullName;
if (tool.type === 'actor-mcp') return tool.actorId;
// For call-actor, the actor name is in `args.actor`.
// The format can be "username/name" or "username/name:toolName" (MCP server Actors).
// Strip the optional `:toolName` suffix to get the base actor name.
const actorArg = args?.actor;
if (typeof actorArg !== 'string') return undefined;
return actorArg.split(':')[0]?.trim() || undefined;
}
type ToolPublicFieldOptions = {
mode?: ServerMode;
filterWidgetMeta?: boolean;
};
/**
* Strips widget-specific metadata (openai/* and ui keys) from tool metadata.
* Used to hide widget metadata in non-apps modes.
*/
function stripWidgetMeta(meta?: ToolBase['_meta']) {
if (!meta) return meta;
const filteredEntries = Object.entries(meta)
.filter(([key]) => !key.startsWith('openai/') && key !== 'ui' && key !== 'ui/resourceUri');
if (filteredEntries.length === 0) return undefined;
return Object.fromEntries(filteredEntries);
}
/**
* Zod 4's z.toJSONSchema() lists properties with `.default()` in `required`.
* Clients treat that as mandatory arguments; strip them before tools/list.
*/
function fixZodInputSchemaRequired(inputSchema: ToolBase['inputSchema']): ToolBase['inputSchema'] {
if (!inputSchema || typeof inputSchema !== 'object') return inputSchema;
return fixZodSchemaRequired({ ...inputSchema } as Record<string, unknown>) as ToolInputSchema;
}
/**
* Returns a public version of the tool containing only fields that should be exposed publicly.
* Used for the tools list request.
*/
export function getToolPublicFieldOnly(tool: ToolBase, options: ToolPublicFieldOptions = {}) {
const { mode, filterWidgetMeta = false } = options;
const meta = filterWidgetMeta && mode !== ServerMode.APPS
? stripWidgetMeta(tool._meta)
: tool._meta;
return {
name: tool.name,
title: tool.title,
description: tool.description,
inputSchema: fixZodInputSchemaRequired(tool.inputSchema),
outputSchema: tool.outputSchema,
annotations: tool.annotations,
icons: tool.icons,
execution: tool.execution,
_meta: meta,
};
}
/**
* Creates a deep copy of a tool entry, preserving functions like ajvValidate and call
* while cloning all other properties to avoid shared state mutations.
*/
export function cloneToolEntry(toolEntry: ToolEntry): ToolEntry {
// Store the original functions
const originalAjvValidate = toolEntry.ajvValidate;
const originalCall = toolEntry.type === 'internal' ? toolEntry.call : undefined;
// Create a deep copy using JSON serialization (excluding functions)
const cloned = JSON.parse(JSON.stringify(toolEntry, (key, value) => {
if (key === 'ajvValidate' || key === 'call') return undefined;
return value;
})) as ToolEntry;
// Restore the original functions
cloned.ajvValidate = originalAjvValidate;
if (toolEntry.type === 'internal' && originalCall) {
(cloned as HelperTool).call = originalCall;
}
return cloned;
}
/** Returns true if the tool is eligible for Skyfire augmentation. */
function isSkyfireEligible(tool: ToolEntry): boolean {
return tool.type === 'actor'
|| (tool.type === 'internal' && SKYFIRE_ENABLED_TOOLS.has(tool.name as HelperTools));
}
/**
* Applies Skyfire augmentation to a tool entry.
* Clones the tool and, if eligible, appends Skyfire instructions to the description
* and adds a `skyfire-pay-id` property to the input schema.
*
* Returns the (possibly augmented) clone if the tool is eligible,
* or the original tool reference if it is not eligible.
* Augmentation is idempotent — calling this on an already-augmented clone is safe.
*/
export function applySkyfireAugmentation(tool: ToolEntry): ToolEntry {
if (!isSkyfireEligible(tool)) return tool;
const cloned = cloneToolEntry(tool);
// Append Skyfire instructions to description (idempotent)
if (cloned.description && !cloned.description.includes(SKYFIRE_TOOL_INSTRUCTIONS)) {
cloned.description += `\n\n${SKYFIRE_TOOL_INSTRUCTIONS}`;
}
// Add skyfire-pay-id property to inputSchema (idempotent)
if (cloned.inputSchema && 'properties' in cloned.inputSchema) {
const props = cloned.inputSchema.properties as Record<string, unknown>;
if (!props['skyfire-pay-id']) {
props['skyfire-pay-id'] = {
type: 'string',
description: SKYFIRE_PAY_ID_PROPERTY_DESCRIPTION,
};
}
}
return Object.freeze(cloned);
}