local-openai2anthropic 0.3.6__py3-none-any.whl → 0.3.7__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.
@@ -3,7 +3,7 @@
3
3
  local-openai2anthropic: A proxy server that converts Anthropic Messages API to OpenAI API.
4
4
  """
5
5
 
6
- __version__ = "0.3.6"
6
+ __version__ = "0.3.7"
7
7
 
8
8
  from local_openai2anthropic.protocol import (
9
9
  AnthropicError,
@@ -88,6 +88,135 @@ websearch_max_uses = 5
88
88
  return True
89
89
 
90
90
 
91
+ def interactive_setup() -> dict:
92
+ """Interactive configuration setup wizard.
93
+
94
+ Guides user through setting up essential configuration values.
95
+
96
+ Returns:
97
+ Dictionary containing user-provided configuration
98
+ """
99
+ print("=" * 60)
100
+ print(" Welcome to local-openai2anthropic Setup Wizard")
101
+ print("=" * 60)
102
+ print()
103
+ print("This wizard will help you create the initial configuration.")
104
+ print(f"Config file will be saved to: {get_config_file()}")
105
+ print()
106
+
107
+ config = {}
108
+
109
+ # OpenAI API Key (required)
110
+ print("[1/3] OpenAI API Configuration")
111
+ print("-" * 40)
112
+ while True:
113
+ api_key = input("Enter your OpenAI API Key (required): ").strip()
114
+ if api_key:
115
+ config["openai_api_key"] = api_key
116
+ break
117
+ print("API Key is required. Please enter a valid key.")
118
+
119
+ # Base URL (optional, with default)
120
+ default_url = "https://api.openai.com/v1"
121
+ base_url = input(f"Enter OpenAI Base URL [{default_url}]: ").strip()
122
+ config["openai_base_url"] = base_url if base_url else default_url
123
+
124
+ print()
125
+ print("[2/3] Server Configuration")
126
+ print("-" * 40)
127
+
128
+ # Host (with default)
129
+ default_host = "0.0.0.0"
130
+ host = input(f"Enter server host [{default_host}]: ").strip()
131
+ config["host"] = host if host else default_host
132
+
133
+ # Port (with default)
134
+ default_port = "8080"
135
+ port_input = input(f"Enter server port [{default_port}]: ").strip()
136
+ try:
137
+ config["port"] = int(port_input) if port_input else int(default_port)
138
+ except ValueError:
139
+ print(f"Invalid port number, using default: {default_port}")
140
+ config["port"] = int(default_port)
141
+
142
+ # API Key for server authentication (optional)
143
+ print()
144
+ print("[3/3] Server API Authentication (Optional)")
145
+ print("-" * 40)
146
+ print("Set an API key to authenticate requests to this server.")
147
+ print(
148
+ "Leave empty to allow unauthenticated access (not recommended for production)."
149
+ )
150
+ server_api_key = input("Enter server API key (optional): ").strip()
151
+ if server_api_key:
152
+ config["api_key"] = server_api_key
153
+
154
+ print()
155
+ print("=" * 60)
156
+ print(" Configuration Summary")
157
+ print("=" * 60)
158
+ print(f"OpenAI Base URL: {config.get('openai_base_url', default_url)}")
159
+ print(
160
+ f"Server: {config.get('host', default_host)}:{config.get('port', default_port)}"
161
+ )
162
+ print(f"OpenAI API Key: {config.get('openai_api_key', '')[:8]}... (configured)")
163
+ if config.get("api_key"):
164
+ print(f"Server Auth: {config['api_key'][:8]}... (configured)")
165
+ print()
166
+
167
+ return config
168
+
169
+
170
+ def create_config_from_dict(config: dict) -> None:
171
+ """Create config file from dictionary.
172
+
173
+ Args:
174
+ config: Dictionary containing configuration values
175
+ """
176
+ import tomli_w
177
+
178
+ config_file = get_config_file()
179
+ config_dir = get_config_dir()
180
+ config_dir.mkdir(parents=True, exist_ok=True)
181
+
182
+ # Set restrictive permissions for the config directory on Unix-like systems
183
+ if sys.platform != "win32":
184
+ config_dir.chmod(0o700)
185
+
186
+ # Build config dict with proper structure
187
+ toml_config: dict = {
188
+ "openai_api_key": config.get("openai_api_key", ""),
189
+ "openai_base_url": config.get("openai_base_url", "https://api.openai.com/v1"),
190
+ "host": config.get("host", "0.0.0.0"),
191
+ "port": config.get("port", 8080),
192
+ "request_timeout": config.get("request_timeout", 300.0),
193
+ "cors_origins": ["*"],
194
+ "cors_credentials": True,
195
+ "cors_methods": ["*"],
196
+ "cors_headers": ["*"],
197
+ "log_level": "INFO",
198
+ "log_dir": "",
199
+ "tavily_timeout": 30.0,
200
+ "tavily_max_results": 5,
201
+ "websearch_max_uses": 5,
202
+ }
203
+
204
+ # Add optional values only if present
205
+ if config.get("api_key"):
206
+ toml_config["api_key"] = config["api_key"]
207
+
208
+ if config.get("tavily_api_key"):
209
+ toml_config["tavily_api_key"] = config["tavily_api_key"]
210
+
211
+ # Write using proper TOML serialization (prevents injection attacks)
212
+ with open(config_file, "wb") as f:
213
+ tomli_w.dump(toml_config, f)
214
+
215
+ # Set restrictive permissions for the config file on Unix-like systems
216
+ if sys.platform != "win32":
217
+ config_file.chmod(0o600)
218
+
219
+
91
220
  def load_config_from_file() -> dict:
92
221
  """Load configuration from TOML file.
93
222
 
@@ -164,18 +293,36 @@ class Settings(BaseModel):
164
293
  return cls(**config_data)
165
294
 
166
295
 
296
+ def is_interactive() -> bool:
297
+ """Check if running in an interactive terminal.
298
+
299
+ Returns:
300
+ True if stdin is a TTY (interactive), False otherwise
301
+ """
302
+ return sys.stdin.isatty()
303
+
304
+
167
305
  @lru_cache
168
306
  def get_settings() -> Settings:
169
307
  """Get cached settings instance.
170
308
 
171
- Creates default config file if it doesn't exist and notifies the user.
309
+ Creates config file interactively if it doesn't exist and running in a TTY.
310
+ Falls back to creating a default config file in non-interactive environments.
172
311
 
173
312
  Returns:
174
313
  Settings instance loaded from config file
175
314
  """
176
- created = create_default_config()
177
- if created:
178
- config_file = get_config_file()
179
- print(f"Created default config file: {config_file}")
180
- print("Please edit it to add your API keys and settings.")
315
+ config_file = get_config_file()
316
+ if not config_file.exists():
317
+ if is_interactive():
318
+ # Interactive setup wizard
319
+ config = interactive_setup()
320
+ create_config_from_dict(config)
321
+ print(f"\nConfiguration saved to: {config_file}")
322
+ print("You can edit this file later to change settings.\n")
323
+ else:
324
+ # Non-interactive environment: create default config
325
+ create_default_config()
326
+ print(f"Created default config file: {config_file}")
327
+ print("Please edit it to add your API keys and settings.")
181
328
  return Settings.from_toml()
@@ -101,7 +101,7 @@ def create_app(settings: Settings | None = None) -> FastAPI:
101
101
  app = FastAPI(
102
102
  title="local-openai2anthropic",
103
103
  description="A proxy server that converts Anthropic Messages API to OpenAI API",
104
- version="0.3.6",
104
+ version="0.3.7",
105
105
  docs_url="/docs",
106
106
  redoc_url="/redoc",
107
107
  )
@@ -253,7 +253,7 @@ Examples:
253
253
  parser.add_argument(
254
254
  "--version",
255
255
  action="version",
256
- version="%(prog)s 0.3.6",
256
+ version="%(prog)s 0.3.7",
257
257
  )
258
258
 
259
259
  # Create subparsers for commands
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: local-openai2anthropic
3
- Version: 0.3.6
3
+ Version: 0.3.7
4
4
  Summary: A lightweight proxy server that converts Anthropic Messages API to OpenAI API
5
5
  Project-URL: Homepage, https://github.com/dongfangzan/local-openai2anthropic
6
6
  Project-URL: Repository, https://github.com/dongfangzan/local-openai2anthropic
@@ -24,7 +24,7 @@ Requires-Dist: httpx>=0.25.0
24
24
  Requires-Dist: openai>=1.30.0
25
25
  Requires-Dist: pydantic-settings>=2.0.0
26
26
  Requires-Dist: pydantic>=2.0.0
27
- Requires-Dist: tomli>=2.0.0; python_version < '3.11'
27
+ Requires-Dist: tomli-w>=1.0.0
28
28
  Requires-Dist: uvicorn[standard]>=0.23.0
29
29
  Provides-Extra: dev
30
30
  Requires-Dist: black>=23.0.0; extra == 'dev'
@@ -1,10 +1,10 @@
1
- local_openai2anthropic/__init__.py,sha256=YEz1wpAzYlPY-zbmlQuLf8gpwLEVJBqff4LdfWcz6NM,1059
1
+ local_openai2anthropic/__init__.py,sha256=ykkqdWKgqvSffE2-GmDcZQFHJRAe0y4d4aeD3fUq_Os,1059
2
2
  local_openai2anthropic/__main__.py,sha256=K21u5u7FN8-DbO67TT_XDF0neGqJeFrVNkteRauCRQk,179
3
- local_openai2anthropic/config.py,sha256=y40uEMBE57dOGCV3w3v5j82ZPZZJWUnJ4yaFZXJ8pRk,4706
3
+ local_openai2anthropic/config.py,sha256=Cjg6J7H7ydKtVSd5m0RlTj-YF6yht3TpF4LcyodqQP4,9621
4
4
  local_openai2anthropic/converter.py,sha256=og94I514M9km_Wbk9c1ddU6fyaQNEbpd2zfpfnBQaTQ,16029
5
5
  local_openai2anthropic/daemon.py,sha256=pZnRojGFcuIpR8yLDNjV-b0LJRBVhgRAa-dKeRRse44,10017
6
6
  local_openai2anthropic/daemon_runner.py,sha256=rguOH0PgpbjqNsKYei0uCQX8JQOQ1wmtQH1CtW95Dbw,3274
7
- local_openai2anthropic/main.py,sha256=3xrjsKFBYK6B8niAtQz0U_yz-eTpf91HnHeAiR9CLQE,12174
7
+ local_openai2anthropic/main.py,sha256=FPCEATNPXvGpkszftdXJh0o0F5sUAOXo2zDagmsWGKI,12174
8
8
  local_openai2anthropic/openai_types.py,sha256=jFdCvLwtXYoo5gGRqOhbHQcVaxcsxNnCP_yFPIv7rG4,3823
9
9
  local_openai2anthropic/protocol.py,sha256=VW3B1YrbYg5UAo7PveQv0Ny5vfuNa6yG6IlHtkuyXiI,5178
10
10
  local_openai2anthropic/router.py,sha256=gwSGCYQGd0tAj4B4cl30UDkIJDIfBP4D8T9KEMKnxyk,16196
@@ -18,8 +18,8 @@ local_openai2anthropic/tools/__init__.py,sha256=OM_6YAwy3G1kbrF7n5NvmBwWPGO0hwq4
18
18
  local_openai2anthropic/tools/handler.py,sha256=SO8AmEUfNIg16s6jOKBaYdajYc0fiI8ciOoiKXIJe_c,14106
19
19
  local_openai2anthropic/utils/__init__.py,sha256=0Apd3lQCmWpQHol4AfjtQe6A3Cpex9Zn-8dyK_FU8Z0,372
20
20
  local_openai2anthropic/utils/tokens.py,sha256=TV3vGAjoGZeyo1xPvwb5jto43p1U1f4HteCApB86X0g,3187
21
- local_openai2anthropic-0.3.6.dist-info/METADATA,sha256=B5M75TvhwturteqT_zxAJ7lXSTAy2QpjTCeDvPRixhQ,11293
22
- local_openai2anthropic-0.3.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
23
- local_openai2anthropic-0.3.6.dist-info/entry_points.txt,sha256=hdc9tSJUNxyNLXcTYye5SuD2K0bEQhxBhGnWTFup6ZM,116
24
- local_openai2anthropic-0.3.6.dist-info/licenses/LICENSE,sha256=X3_kZy3lJvd_xp8IeyUcIAO2Y367MXZc6aaRx8BYR_s,11369
25
- local_openai2anthropic-0.3.6.dist-info/RECORD,,
21
+ local_openai2anthropic-0.3.7.dist-info/METADATA,sha256=ZqgaeWvxJAKD1fDKC0XWiSwf3BI1DbH5UQpjvOzFewo,11270
22
+ local_openai2anthropic-0.3.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
23
+ local_openai2anthropic-0.3.7.dist-info/entry_points.txt,sha256=hdc9tSJUNxyNLXcTYye5SuD2K0bEQhxBhGnWTFup6ZM,116
24
+ local_openai2anthropic-0.3.7.dist-info/licenses/LICENSE,sha256=X3_kZy3lJvd_xp8IeyUcIAO2Y367MXZc6aaRx8BYR_s,11369
25
+ local_openai2anthropic-0.3.7.dist-info/RECORD,,