arcade-core 4.1.0__py3-none-any.whl → 4.2.1__py3-none-any.whl

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.
@@ -0,0 +1,34 @@
1
+ """Converters for transforming tool definitions between formats."""
2
+
3
+ from .anthropic import (
4
+ AnthropicInputSchema,
5
+ AnthropicInputSchemaProperty,
6
+ AnthropicToolList,
7
+ AnthropicToolSchema,
8
+ to_anthropic,
9
+ )
10
+ from .openai import (
11
+ OpenAIFunctionParameterProperty,
12
+ OpenAIFunctionParameters,
13
+ OpenAIFunctionSchema,
14
+ OpenAIToolList,
15
+ OpenAIToolSchema,
16
+ to_openai,
17
+ )
18
+ from .utils import denormalize_tool_name, normalize_tool_name
19
+
20
+ __all__ = [
21
+ "AnthropicInputSchema",
22
+ "AnthropicInputSchemaProperty",
23
+ "AnthropicToolList",
24
+ "AnthropicToolSchema",
25
+ "OpenAIFunctionParameterProperty",
26
+ "OpenAIFunctionParameters",
27
+ "OpenAIFunctionSchema",
28
+ "OpenAIToolList",
29
+ "OpenAIToolSchema",
30
+ "denormalize_tool_name",
31
+ "normalize_tool_name",
32
+ "to_anthropic",
33
+ "to_openai",
34
+ ]
@@ -0,0 +1,194 @@
1
+ """Converter for converting Arcade ToolDefinition to Anthropic tool schema."""
2
+
3
+ from typing import Any, TypedDict
4
+
5
+ from arcade_core.catalog import MaterializedTool
6
+ from arcade_core.converters.utils import normalize_tool_name
7
+ from arcade_core.schema import InputParameter, ValueSchema
8
+
9
+ # ----------------------------------------------------------------------------
10
+ # Type definitions for JSON tool schemas used by Anthropic APIs.
11
+ # Defines the proper types for tool schemas to ensure
12
+ # compatibility with Anthropic's Messages API tool use feature.
13
+ # Reference: https://docs.anthropic.com/en/docs/build-with-claude/tool-use
14
+ # ----------------------------------------------------------------------------
15
+
16
+
17
+ class AnthropicInputSchemaProperty(TypedDict, total=False):
18
+ """Type definition for a property within Anthropic input schema."""
19
+
20
+ type: str
21
+ """The JSON Schema type for this property."""
22
+
23
+ description: str
24
+ """Description of the property."""
25
+
26
+ enum: list[Any]
27
+ """Allowed values for enum properties."""
28
+
29
+ items: dict[str, Any]
30
+ """Schema for array items when type is 'array'."""
31
+
32
+ properties: dict[str, "AnthropicInputSchemaProperty"]
33
+ """Nested properties when type is 'object'."""
34
+
35
+ required: list[str]
36
+ """Required fields for nested objects."""
37
+
38
+
39
+ class AnthropicInputSchema(TypedDict, total=False):
40
+ """Type definition for Anthropic tool input schema."""
41
+
42
+ type: str
43
+ """Must be 'object' for tool input schemas."""
44
+
45
+ properties: dict[str, AnthropicInputSchemaProperty]
46
+ """The properties of the tool input parameters."""
47
+
48
+ required: list[str]
49
+ """List of required parameter names."""
50
+
51
+
52
+ class AnthropicToolSchema(TypedDict, total=False):
53
+ """
54
+ Schema for a tool definition passed to Anthropic's `tools` parameter.
55
+
56
+ Unlike OpenAI, Anthropic uses a flat structure without a wrapper object.
57
+ The schema uses `input_schema` instead of `parameters`.
58
+ """
59
+
60
+ name: str
61
+ """The name of the tool."""
62
+
63
+ description: str
64
+ """Description of what the tool does."""
65
+
66
+ input_schema: AnthropicInputSchema
67
+ """JSON Schema describing the tool's input parameters."""
68
+
69
+
70
+ # Type alias for a list of Anthropic tool schemas
71
+ AnthropicToolList = list[AnthropicToolSchema]
72
+
73
+
74
+ # ----------------------------------------------------------------------------
75
+ # Converters
76
+ # ----------------------------------------------------------------------------
77
+ def to_anthropic(tool: MaterializedTool) -> AnthropicToolSchema:
78
+ """Convert a MaterializedTool to Anthropic tool schema format.
79
+
80
+ Args:
81
+ tool: The MaterializedTool to convert
82
+
83
+ Returns:
84
+ The Anthropic tool schema format (what is passed to the Anthropic API)
85
+ """
86
+ name = normalize_tool_name(tool.definition.fully_qualified_name)
87
+ description = tool.description
88
+ input_schema = _convert_input_parameters_to_json_schema(tool.definition.input.parameters)
89
+
90
+ return _create_tool_schema(name, description, input_schema)
91
+
92
+
93
+ def _create_tool_schema(
94
+ name: str, description: str, input_schema: AnthropicInputSchema
95
+ ) -> AnthropicToolSchema:
96
+ """Create a properly typed Anthropic tool schema.
97
+
98
+ Args:
99
+ name: The name of the tool
100
+ description: Description of what the tool does
101
+ input_schema: JSON schema for the tool input parameters
102
+
103
+ Returns:
104
+ A properly typed AnthropicToolSchema
105
+ """
106
+ tool: AnthropicToolSchema = {
107
+ "name": name,
108
+ "description": description,
109
+ "input_schema": input_schema,
110
+ }
111
+
112
+ return tool
113
+
114
+
115
+ def _convert_value_schema_to_json_schema(
116
+ value_schema: ValueSchema,
117
+ ) -> AnthropicInputSchemaProperty:
118
+ """Convert Arcade ValueSchema to JSON Schema format for Anthropic."""
119
+ type_mapping = {
120
+ "string": "string",
121
+ "integer": "integer",
122
+ "number": "number",
123
+ "boolean": "boolean",
124
+ "json": "object",
125
+ "array": "array",
126
+ }
127
+
128
+ schema: AnthropicInputSchemaProperty = {"type": type_mapping[value_schema.val_type]}
129
+
130
+ if value_schema.val_type == "array" and value_schema.inner_val_type:
131
+ items_schema: dict[str, Any] = {"type": type_mapping[value_schema.inner_val_type]}
132
+
133
+ # For arrays, enum should be applied to the items, not the array itself
134
+ if value_schema.enum:
135
+ items_schema["enum"] = value_schema.enum
136
+
137
+ schema["items"] = items_schema
138
+ else:
139
+ # Handle enum for non-array types
140
+ if value_schema.enum:
141
+ schema["enum"] = value_schema.enum
142
+
143
+ # Handle object properties
144
+ if value_schema.val_type == "json" and value_schema.properties:
145
+ schema["properties"] = {
146
+ name: _convert_value_schema_to_json_schema(nested_schema)
147
+ for name, nested_schema in value_schema.properties.items()
148
+ }
149
+
150
+ return schema
151
+
152
+
153
+ def _convert_input_parameters_to_json_schema(
154
+ parameters: list[InputParameter],
155
+ ) -> AnthropicInputSchema:
156
+ """Convert list of InputParameter to JSON schema parameters object.
157
+
158
+ Unlike OpenAI's strict mode, Anthropic uses standard JSON Schema:
159
+ - Only actually required parameters are listed in 'required'
160
+ - No need to add 'null' to optional parameter types
161
+ - No 'additionalProperties: false' requirement
162
+ """
163
+ if not parameters:
164
+ # Minimal JSON schema for a tool with no input parameters
165
+ return {
166
+ "type": "object",
167
+ "properties": {},
168
+ }
169
+
170
+ properties: dict[str, AnthropicInputSchemaProperty] = {}
171
+ required: list[str] = []
172
+
173
+ for parameter in parameters:
174
+ param_schema = _convert_value_schema_to_json_schema(parameter.value_schema)
175
+
176
+ if parameter.description:
177
+ param_schema["description"] = parameter.description
178
+
179
+ properties[parameter.name] = param_schema
180
+
181
+ # Only add actually required parameters to the required list
182
+ if parameter.required:
183
+ required.append(parameter.name)
184
+
185
+ json_schema: AnthropicInputSchema = {
186
+ "type": "object",
187
+ "properties": properties,
188
+ }
189
+
190
+ # Only include 'required' if there are required parameters
191
+ if required:
192
+ json_schema["required"] = required
193
+
194
+ return json_schema
@@ -3,6 +3,7 @@
3
3
  from typing import Any, Literal, TypedDict
4
4
 
5
5
  from arcade_core.catalog import MaterializedTool
6
+ from arcade_core.converters.utils import normalize_tool_name
6
7
  from arcade_core.schema import InputParameter, ValueSchema
7
8
 
8
9
  # ----------------------------------------------------------------------------
@@ -101,7 +102,7 @@ def to_openai(tool: MaterializedTool) -> OpenAIToolSchema:
101
102
  Returns:
102
103
  The OpenAI JsonToolSchema format (what is passed to the OpenAI API)
103
104
  """
104
- name = tool.definition.fully_qualified_name.replace(".", "_")
105
+ name = normalize_tool_name(tool.definition.fully_qualified_name)
105
106
  description = tool.description
106
107
  parameters_schema = _convert_input_parameters_to_json_schema(tool.definition.input.parameters)
107
108
  return _create_tool_schema(name, description, parameters_schema)
@@ -0,0 +1,54 @@
1
+ """Shared utilities for tool name conversion across providers.
2
+
3
+ This module contains common utilities used by both OpenAI and Anthropic converters.
4
+ """
5
+
6
+
7
+ def normalize_tool_name(name: str) -> str:
8
+ """
9
+ Normalize a tool name for LLM provider compatibility.
10
+
11
+ Both OpenAI and Anthropic have restrictions on tool names:
12
+ - OpenAI: allows alphanumeric, hyphens, underscores (max 64 chars)
13
+ - Anthropic: allows alphanumeric and underscores only (no dots)
14
+
15
+ Arcade uses dot notation for fully qualified names (e.g., "Google.Search"),
16
+ so we normalize by replacing dots with underscores.
17
+
18
+ Args:
19
+ name: The original tool name (e.g., "Google.Search")
20
+
21
+ Returns:
22
+ The normalized tool name (e.g., "Google_Search")
23
+
24
+ Examples:
25
+ >>> normalize_tool_name("Google.Search")
26
+ 'Google_Search'
27
+ >>> normalize_tool_name("MyTool")
28
+ 'MyTool'
29
+ >>> normalize_tool_name("Namespace.Sub.Tool")
30
+ 'Namespace_Sub_Tool'
31
+ """
32
+ return name.replace(".", "_")
33
+
34
+
35
+ def denormalize_tool_name(normalized_name: str, separator: str = ".") -> str:
36
+ """
37
+ Reverse the normalization of a tool name.
38
+
39
+ This converts provider-format names back to Arcade's dot notation.
40
+ Note: This is a best-effort reversal and may not be accurate if the original
41
+ name contained underscores.
42
+
43
+ Args:
44
+ normalized_name: The normalized tool name (e.g., "Google_Search")
45
+ separator: The separator to use (default: ".")
46
+
47
+ Returns:
48
+ The denormalized tool name (e.g., "Google.Search")
49
+
50
+ Examples:
51
+ >>> denormalize_tool_name("Google_Search")
52
+ 'Google.Search'
53
+ """
54
+ return normalized_name.replace("_", separator)
@@ -6,7 +6,6 @@ supporting pre-login anonymous tracking, post-login identity stitching,
6
6
  and logout identity rotation.
7
7
  """
8
8
 
9
- import fcntl
10
9
  import json
11
10
  import os
12
11
  import tempfile
@@ -14,6 +13,7 @@ import uuid
14
13
  from typing import Any
15
14
 
16
15
  import httpx
16
+ import portalocker
17
17
  import yaml
18
18
 
19
19
  from arcade_core.constants import ARCADE_CONFIG_PATH, CREDENTIALS_FILE_PATH
@@ -46,18 +46,16 @@ class UsageIdentity:
46
46
  if os.path.exists(self.usage_file_path):
47
47
  try:
48
48
  with open(self.usage_file_path) as f:
49
- # lock file
50
- if os.name != "nt": # Unix-like systems
51
- fcntl.flock(f.fileno(), fcntl.LOCK_SH)
49
+ # Lock file for reading (shared lock)
50
+ portalocker.lock(f, portalocker.LOCK_SH)
52
51
  try:
53
52
  data = json.load(f)
54
53
  if isinstance(data, dict) and KEY_ANON_ID in data:
55
54
  self._data = data
56
55
  return self._data
57
56
  finally:
58
- # unlock file
59
- if os.name != "nt":
60
- fcntl.flock(f.fileno(), fcntl.LOCK_UN)
57
+ # Unlock file
58
+ portalocker.unlock(f)
61
59
  except Exception: # noqa: S110
62
60
  pass
63
61
 
@@ -80,18 +78,18 @@ class UsageIdentity:
80
78
 
81
79
  try:
82
80
  with os.fdopen(temp_fd, "w") as f:
83
- # lock file
84
- if os.name != "nt": # Unix-like systems
85
- fcntl.flock(f.fileno(), fcntl.LOCK_EX)
81
+ # Lock file for writing (exclusive lock)
82
+ portalocker.lock(f, portalocker.LOCK_EX)
86
83
  try:
87
84
  json.dump(data, f, indent=2)
88
85
  f.flush()
89
86
  os.fsync(f.fileno()) # ensure data is written to disk
90
87
  finally:
91
- if os.name != "nt":
92
- fcntl.flock(f.fileno(), fcntl.LOCK_UN)
88
+ portalocker.unlock(f)
93
89
 
94
- os.rename(temp_path, self.usage_file_path)
90
+ # Use os.replace() for cross-platform atomic file replacement
91
+ # os.replace() is atomic on both Unix and Windows (Python 3.3+)
92
+ os.replace(temp_path, self.usage_file_path)
95
93
  except Exception:
96
94
  # clean up
97
95
  import contextlib
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arcade-core
3
- Version: 4.1.0
3
+ Version: 4.2.1
4
4
  Summary: Arcade Core - Core library for Arcade platform
5
5
  Author-email: Arcade <dev@arcade.dev>
6
6
  License: MIT
@@ -16,6 +16,7 @@ Requires-Python: >=3.10
16
16
  Requires-Dist: httpx>=0.27.0
17
17
  Requires-Dist: loguru>=0.7.0
18
18
  Requires-Dist: packaging>=24.1
19
+ Requires-Dist: portalocker>=2.10.0
19
20
  Requires-Dist: pydantic>=2.7.0
20
21
  Requires-Dist: pyjwt>=2.8.0
21
22
  Requires-Dist: pyyaml>=6.0
@@ -17,15 +17,18 @@ arcade_core/schema.py,sha256=y32Ndlb1b3Hw7_Wm3eVXxh0kSg3oGitWUYBfwFLjHuY,21198
17
17
  arcade_core/toolkit.py,sha256=lLlOL6fA6Lmo-dtLTMMcPCzKDf9YQObwxG1LdVADv3E,14431
18
18
  arcade_core/utils.py,sha256=_3bM-yfIDFmMVqt-NFYp2Lx1QcNWp7xytGjUQzPs2LY,3255
19
19
  arcade_core/version.py,sha256=CpXi3jGlx23RvRyU7iytOMZrnspdWw4yofS8lpP1AJU,18
20
- arcade_core/converters/openai.py,sha256=4efdgTkvdwT44VGStBhdUmzCnoP5dysceIqPVVPG-vk,7408
20
+ arcade_core/converters/__init__.py,sha256=ckb7c0LCq62_bARxoV7XlRYF7vtGbG1SJnDTWZ9Z7P4,827
21
+ arcade_core/converters/anthropic.py,sha256=Spkf6crPGPF8O82nyxqYI3whRs9togDqoTzTVYaZ6fc,6175
22
+ arcade_core/converters/openai.py,sha256=J4kQlmUzyZjp2vsnS0iDxyF_NhN3NfegiFTl7xYCwXM,7472
23
+ arcade_core/converters/utils.py,sha256=syN6G88UA8sChiLlr1VBna29sczuevb76_OAYuUfvtE,1699
21
24
  arcade_core/network/__init__.py,sha256=8GEcg6QAr63U8U1n4bnf7cL-F7FrUrc9otHM2J9ODwk,53
22
25
  arcade_core/network/org_transport.py,sha256=DZdIYdldtYZ6gtmWA1C8dXmsNuOv7tePhlpBuAvkhRQ,3222
23
26
  arcade_core/usage/__init__.py,sha256=SUR5mqF-bjdbl-P-OOHN6OFAjXZu4agXyPhr7xdVXCw,234
24
27
  arcade_core/usage/__main__.py,sha256=rSJkE1G9hlV3HRRA6EJE5Lmy3wKyan7rAxBXHX9A1cI,1577
25
28
  arcade_core/usage/constants.py,sha256=1FQIhkFFMZUhU-H4A7GvMb7KQ3qLFrNAZb2-LEvSF3k,1052
26
- arcade_core/usage/identity.py,sha256=egclRR26jGP1vVvoOoaaZdcS4AtSTZ8fLHpBq1HRgHw,8452
29
+ arcade_core/usage/identity.py,sha256=h_THm-cN3j8nqW7_Lew7OdBRA70valCKJ7zvhDvja_E,8416
27
30
  arcade_core/usage/usage_service.py,sha256=xzWWSEktm58liiNYugBHRactSru8V5foriHcsoH0j1A,3407
28
31
  arcade_core/usage/utils.py,sha256=FqBOmlhwT68cbnpI5Vx9ZW6vLRYPVg4FJ0GaMEp8qEM,398
29
- arcade_core-4.1.0.dist-info/METADATA,sha256=LJ8qsm5pS6p17nHRSfnsFiCCzGFtpSDdkmMW4mIXHig,2412
30
- arcade_core-4.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
31
- arcade_core-4.1.0.dist-info/RECORD,,
32
+ arcade_core-4.2.1.dist-info/METADATA,sha256=5lFJ1C5vqqBXHLC51RdiGvHFsddE-JbG69wt8MFydlM,2447
33
+ arcade_core-4.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
34
+ arcade_core-4.2.1.dist-info/RECORD,,