mycode-sdk 0.9.4__tar.gz → 0.13.3__tar.gz

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.
Files changed (28) hide show
  1. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/PKG-INFO +78 -35
  2. mycode_sdk-0.13.3/README.md +213 -0
  3. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/pyproject.toml +8 -7
  4. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/src/mycode/__init__.py +12 -10
  5. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/src/mycode/agent.py +546 -140
  6. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/src/mycode/attachments.py +7 -10
  7. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/src/mycode/compact.py +32 -4
  8. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/src/mycode/hooks.py +12 -7
  9. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/src/mycode/messages.py +51 -10
  10. mycode_sdk-0.13.3/src/mycode/models.py +278 -0
  11. mycode_sdk-0.13.3/src/mycode/models_catalog.json +11700 -0
  12. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/src/mycode/providers/__init__.py +21 -2
  13. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/src/mycode/providers/anthropic_like.py +80 -58
  14. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/src/mycode/providers/base.py +159 -10
  15. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/src/mycode/providers/gemini.py +130 -43
  16. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/src/mycode/providers/openai_chat.py +180 -68
  17. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/src/mycode/providers/openai_responses.py +106 -44
  18. mycode_sdk-0.13.3/src/mycode/session.py +153 -0
  19. mycode_sdk-0.13.3/src/mycode/tools.py +375 -0
  20. mycode_sdk-0.9.4/README.md +0 -171
  21. mycode_sdk-0.9.4/src/mycode/models.py +0 -157
  22. mycode_sdk-0.9.4/src/mycode/models_catalog.json +0 -3294
  23. mycode_sdk-0.9.4/src/mycode/session.py +0 -324
  24. mycode_sdk-0.9.4/src/mycode/tools.py +0 -1045
  25. mycode_sdk-0.9.4/src/mycode/utils.py +0 -46
  26. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/.gitignore +0 -0
  27. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/LICENSE +0 -0
  28. {mycode_sdk-0.9.4 → mycode_sdk-0.13.3}/src/mycode/py.typed +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: mycode-sdk
3
- Version: 0.9.4
3
+ Version: 0.13.3
4
4
  Summary: Lightweight Python SDK for building AI agents.
5
5
  Project-URL: Homepage, https://github.com/legibet/mycode
6
6
  Project-URL: Repository, https://github.com/legibet/mycode
@@ -18,16 +18,17 @@ Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Classifier: Topic :: Software Development
20
20
  Requires-Python: >=3.12
21
- Requires-Dist: anthropic>=0.106.0
22
- Requires-Dist: google-genai>=2.8.0
23
- Requires-Dist: griffelib>=2.0.0
24
- Requires-Dist: openai>=2.41.0
25
- Requires-Dist: pydantic>=2.13.0
21
+ Requires-Dist: anthropic>=0.116.0
22
+ Requires-Dist: google-genai>=2.15.0
23
+ Requires-Dist: griffelib>=2.1.0
24
+ Requires-Dist: httpx>=0.28.0
25
+ Requires-Dist: openai>=2.45.0
26
+ Requires-Dist: pydantic>=2.13.4
26
27
  Description-Content-Type: text/markdown
27
28
 
28
29
  # mycode-sdk
29
30
 
30
- Lightweight Python SDK for building AI agents.
31
+ Lightweight Python SDK for building AI agents. Multi-turn conversations, tool calling, session persistence, and streaming events. Provider adapters for Anthropic, OpenAI, Google, and more.
31
32
 
32
33
  ## Install
33
34
 
@@ -41,15 +42,27 @@ pip install mycode-sdk
41
42
 
42
43
  ```python
43
44
  import asyncio
45
+ from pathlib import Path
46
+
47
+ from mycode import Agent, tool
48
+
44
49
 
45
- from mycode import Agent, bash_tool, read_tool
50
+ @tool
51
+ def read_file(path: str) -> str:
52
+ """Read a UTF-8 text file.
53
+
54
+ Args:
55
+ path: File path, relative to the working directory.
56
+ """
57
+
58
+ return Path(path).read_text(encoding="utf-8")
46
59
 
47
60
 
48
61
  async def main() -> None:
49
62
  agent = Agent(
50
63
  model="claude-sonnet-4-6",
51
64
  api_key="YOUR_API_KEY",
52
- tools=[read_tool, bash_tool],
65
+ tools=[read_file],
53
66
  )
54
67
 
55
68
  async for event in agent.achat("Read pyproject.toml and tell me the project name."):
@@ -62,16 +75,36 @@ asyncio.run(main())
62
75
 
63
76
  `Agent(...)` infers the provider from the model id. No tools are registered unless you pass `tools=[...]`.
64
77
 
65
- For a simple synchronous call, use `run()`:
78
+ For a synchronous call, use `run()`:
66
79
 
67
80
  ```python
68
81
  result = agent.run("Read pyproject.toml and tell me the project name.")
69
82
  print(result.text)
70
83
  ```
71
84
 
85
+ ## Providers
86
+
87
+ The SDK infers the provider from the model string (`claude-*` to Anthropic, `gpt-*` to OpenAI, etc.). API keys are auto-discovered from environment variables:
88
+
89
+ | Provider | id | Env var |
90
+ | --- | --- | --- |
91
+ | Anthropic | `anthropic` | `ANTHROPIC_API_KEY` |
92
+ | OpenAI | `openai` | `OPENAI_API_KEY` |
93
+ | Google Gemini | `google` | `GEMINI_API_KEY` |
94
+ | Moonshot | `moonshotai` | `MOONSHOT_API_KEY` |
95
+ | MiniMax | `minimax` | `MINIMAX_API_KEY` |
96
+ | DeepSeek | `deepseek` | `DEEPSEEK_API_KEY` |
97
+ | Z.AI | `zai` | `ZAI_API_KEY` |
98
+ | OpenRouter | `openrouter` | `OPENROUTER_API_KEY` |
99
+ | Alibaba Cloud | `alibaba` | `DASHSCOPE_API_KEY` |
100
+ | xAI | `xai` | `XAI_API_KEY` |
101
+ | OpenAI-compatible | `openai_chat` | - |
102
+
103
+ Pass `api_key=` to override the env var, `api_base=` for a custom endpoint. Model metadata is bundled from [models.dev](https://models.dev); pass `context_window`, `supports_reasoning`, `supports_image_input`, or `supports_pdf_input` to override.
104
+
72
105
  ## Multi-turn conversations
73
106
 
74
- Call `achat()` or `run()` again on the same `Agent` history accumulates automatically:
107
+ Call `achat()` or `run()` again on the same `Agent` to continue:
75
108
 
76
109
  ```python
77
110
  agent = Agent(model="claude-sonnet-4-6", api_key="...")
@@ -80,6 +113,8 @@ agent.run("What is 2 + 2?")
80
113
  agent.run("Now multiply that by 10.") # remembers the earlier answer
81
114
  ```
82
115
 
116
+ `agent.clear()` drops in-memory history. `agent.messages` accumulates across calls.
117
+
83
118
  ## Attachments
84
119
 
85
120
  Pass `attachments` to `achat()` or `run()` to add files alongside the prompt:
@@ -89,7 +124,7 @@ from mycode import Attachment
89
124
 
90
125
  agent.run("Describe these.", attachments=["diagram.png", "report.pdf", "notes.txt"])
91
126
 
92
- # Or build them explicitly — raw bytes and inline text never touch the disk:
127
+ # Or build them explicitly:
93
128
  agent.run(
94
129
  "Review.",
95
130
  attachments=[
@@ -100,7 +135,7 @@ agent.run(
100
135
  )
101
136
  ```
102
137
 
103
- Images support `image/png`, `image/jpeg`, `image/gif`, `image/webp`; documents support `application/pdf`. Sending an image or PDF to a model that lacks that capability yields an `error` event; a bad path or unsupported type raises `ValueError` before the model is called.
138
+ Images support `image/png`, `image/jpeg`, `image/gif`, `image/webp`; documents support `application/pdf`. Sending an image or PDF to a model without that capability yields an `error` event. A bad path or unsupported type raises `ValueError` before the provider is called.
104
139
 
105
140
  ## Saving sessions
106
141
 
@@ -117,17 +152,9 @@ agent = Agent(
117
152
  )
118
153
  ```
119
154
 
120
- Construct another `Agent` with the same `(session_dir, session_id)` later to resume the conversation — the history is loaded automatically.
155
+ Construct another `Agent` with the same `(session_dir, session_id)` to load the conversation history.
121
156
 
122
- ## Built-in tools
123
-
124
- ```python
125
- from mycode import read_tool, write_tool, edit_tool, bash_tool
126
- ```
127
-
128
- Four tools for reading, writing, editing files, and running shell commands. Opt in by passing them to `tools=[...]`.
129
-
130
- ## Custom tools
157
+ ## Tools
131
158
 
132
159
  Decorate a typed function with `@tool`:
133
160
 
@@ -153,34 +180,48 @@ agent = Agent(
153
180
  )
154
181
  ```
155
182
 
156
- To call a built-in tool from inside your own tool, type the first parameter as `ToolContext`:
183
+ To call another registered tool from inside a tool, type the first parameter as `ToolContext` and dispatch by name:
157
184
 
158
185
  ```python
159
186
  from mycode import ToolContext, tool
160
187
 
161
188
 
162
189
  @tool
163
- def summarize_file(ctx: ToolContext, path: str) -> str:
164
- """Return the first line of a text file."""
190
+ def greet_team(ctx: ToolContext, names: list[str]) -> str:
191
+ """Greet several people at once."""
165
192
 
166
- result = ctx.read(path)
167
- return result.output.splitlines()[0] if result.output else ""
193
+ return "\n".join(ctx.call("greet", {"name": name}).output for name in names)
168
194
  ```
169
195
 
196
+ Async tools use `await ctx.acall(name, args)` instead.
197
+
170
198
  ## Tool hooks
171
199
 
172
200
  Inspect or replace tool calls before they run. Return `None` from `before_tool` to let the tool execute, or a `ToolExecutionResult` to skip it:
173
201
 
174
202
  ```python
175
- from mycode import Agent, Hooks, ToolExecutionResult, bash_tool
203
+ import os
204
+
205
+ from mycode import Agent, Hooks, ToolExecutionResult, tool
176
206
 
177
207
  hooks = Hooks()
178
208
 
179
209
 
210
+ @tool
211
+ def delete_file(path: str) -> str:
212
+ """Delete a file.
213
+
214
+ Args:
215
+ path: Path of the file to delete.
216
+ """
217
+
218
+ os.remove(path)
219
+ return f"deleted {path}"
220
+
221
+
180
222
  @hooks.before_tool
181
- async def block_rm(ctx):
182
- cmd = str(ctx.tool_input.get("command") or "")
183
- if ctx.tool_name == "bash" and "rm -rf" in cmd:
223
+ async def protect_dotfiles(ctx):
224
+ if ctx.tool_name == "delete_file" and str(ctx.tool_input.get("path") or "").startswith("."):
184
225
  return ToolExecutionResult(output="error: blocked", is_error=True)
185
226
  return None
186
227
 
@@ -188,11 +229,13 @@ async def block_rm(ctx):
188
229
  agent = Agent(
189
230
  model="claude-sonnet-4-6",
190
231
  api_key="...",
191
- tools=[bash_tool],
232
+ tools=[delete_file],
192
233
  hooks=hooks,
193
234
  )
194
235
  ```
195
236
 
196
237
  `@hooks.after_tool` runs after the tool and can replace the result (audit, redact, etc.).
197
238
 
198
- See [docs/sdk.md](../docs/sdk.md) for the event stream, cancellation, sessions, and the full `Agent` / `@tool` reference.
239
+ ## Further reading
240
+
241
+ See [docs/sdk.md](https://github.com/legibet/mycode/blob/main/docs/sdk.md) for the streaming event API, cancellation, retries, compaction, session internals, and the full `Agent` / `@tool` reference.
@@ -0,0 +1,213 @@
1
+ # mycode-sdk
2
+
3
+ Lightweight Python SDK for building AI agents. Multi-turn conversations, tool calling, session persistence, and streaming events. Provider adapters for Anthropic, OpenAI, Google, and more.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ uv add mycode-sdk
9
+ # or
10
+ pip install mycode-sdk
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```python
16
+ import asyncio
17
+ from pathlib import Path
18
+
19
+ from mycode import Agent, tool
20
+
21
+
22
+ @tool
23
+ def read_file(path: str) -> str:
24
+ """Read a UTF-8 text file.
25
+
26
+ Args:
27
+ path: File path, relative to the working directory.
28
+ """
29
+
30
+ return Path(path).read_text(encoding="utf-8")
31
+
32
+
33
+ async def main() -> None:
34
+ agent = Agent(
35
+ model="claude-sonnet-4-6",
36
+ api_key="YOUR_API_KEY",
37
+ tools=[read_file],
38
+ )
39
+
40
+ async for event in agent.achat("Read pyproject.toml and tell me the project name."):
41
+ if event.type == "text":
42
+ print(event.data["delta"], end="", flush=True)
43
+
44
+
45
+ asyncio.run(main())
46
+ ```
47
+
48
+ `Agent(...)` infers the provider from the model id. No tools are registered unless you pass `tools=[...]`.
49
+
50
+ For a synchronous call, use `run()`:
51
+
52
+ ```python
53
+ result = agent.run("Read pyproject.toml and tell me the project name.")
54
+ print(result.text)
55
+ ```
56
+
57
+ ## Providers
58
+
59
+ The SDK infers the provider from the model string (`claude-*` to Anthropic, `gpt-*` to OpenAI, etc.). API keys are auto-discovered from environment variables:
60
+
61
+ | Provider | id | Env var |
62
+ | --- | --- | --- |
63
+ | Anthropic | `anthropic` | `ANTHROPIC_API_KEY` |
64
+ | OpenAI | `openai` | `OPENAI_API_KEY` |
65
+ | Google Gemini | `google` | `GEMINI_API_KEY` |
66
+ | Moonshot | `moonshotai` | `MOONSHOT_API_KEY` |
67
+ | MiniMax | `minimax` | `MINIMAX_API_KEY` |
68
+ | DeepSeek | `deepseek` | `DEEPSEEK_API_KEY` |
69
+ | Z.AI | `zai` | `ZAI_API_KEY` |
70
+ | OpenRouter | `openrouter` | `OPENROUTER_API_KEY` |
71
+ | Alibaba Cloud | `alibaba` | `DASHSCOPE_API_KEY` |
72
+ | xAI | `xai` | `XAI_API_KEY` |
73
+ | OpenAI-compatible | `openai_chat` | - |
74
+
75
+ Pass `api_key=` to override the env var, `api_base=` for a custom endpoint. Model metadata is bundled from [models.dev](https://models.dev); pass `context_window`, `supports_reasoning`, `supports_image_input`, or `supports_pdf_input` to override.
76
+
77
+ ## Multi-turn conversations
78
+
79
+ Call `achat()` or `run()` again on the same `Agent` to continue:
80
+
81
+ ```python
82
+ agent = Agent(model="claude-sonnet-4-6", api_key="...")
83
+
84
+ agent.run("What is 2 + 2?")
85
+ agent.run("Now multiply that by 10.") # remembers the earlier answer
86
+ ```
87
+
88
+ `agent.clear()` drops in-memory history. `agent.messages` accumulates across calls.
89
+
90
+ ## Attachments
91
+
92
+ Pass `attachments` to `achat()` or `run()` to add files alongside the prompt:
93
+
94
+ ```python
95
+ from mycode import Attachment
96
+
97
+ agent.run("Describe these.", attachments=["diagram.png", "report.pdf", "notes.txt"])
98
+
99
+ # Or build them explicitly:
100
+ agent.run(
101
+ "Review.",
102
+ attachments=[
103
+ Attachment.path("diagram.png"),
104
+ Attachment.bytes(png_data, media_type="image/png"),
105
+ Attachment.text("TODO: ship it", name="note.md"),
106
+ ],
107
+ )
108
+ ```
109
+
110
+ Images support `image/png`, `image/jpeg`, `image/gif`, `image/webp`; documents support `application/pdf`. Sending an image or PDF to a model without that capability yields an `error` event. A bad path or unsupported type raises `ValueError` before the provider is called.
111
+
112
+ ## Saving sessions
113
+
114
+ Pass `session_dir` to persist the conversation to disk. Each session lives in a subdirectory named by `session_id`:
115
+
116
+ ```python
117
+ from pathlib import Path
118
+
119
+ agent = Agent(
120
+ model="claude-sonnet-4-6",
121
+ api_key="...",
122
+ session_dir=Path("./chats"),
123
+ session_id="my-chat",
124
+ )
125
+ ```
126
+
127
+ Construct another `Agent` with the same `(session_dir, session_id)` to load the conversation history.
128
+
129
+ ## Tools
130
+
131
+ Decorate a typed function with `@tool`:
132
+
133
+ ```python
134
+ from mycode import Agent, tool
135
+
136
+
137
+ @tool
138
+ def greet(name: str) -> str:
139
+ """Return a friendly greeting.
140
+
141
+ Args:
142
+ name: Person name.
143
+ """
144
+
145
+ return f"hello, {name}"
146
+
147
+
148
+ agent = Agent(
149
+ model="claude-sonnet-4-6",
150
+ api_key="...",
151
+ tools=[greet],
152
+ )
153
+ ```
154
+
155
+ To call another registered tool from inside a tool, type the first parameter as `ToolContext` and dispatch by name:
156
+
157
+ ```python
158
+ from mycode import ToolContext, tool
159
+
160
+
161
+ @tool
162
+ def greet_team(ctx: ToolContext, names: list[str]) -> str:
163
+ """Greet several people at once."""
164
+
165
+ return "\n".join(ctx.call("greet", {"name": name}).output for name in names)
166
+ ```
167
+
168
+ Async tools use `await ctx.acall(name, args)` instead.
169
+
170
+ ## Tool hooks
171
+
172
+ Inspect or replace tool calls before they run. Return `None` from `before_tool` to let the tool execute, or a `ToolExecutionResult` to skip it:
173
+
174
+ ```python
175
+ import os
176
+
177
+ from mycode import Agent, Hooks, ToolExecutionResult, tool
178
+
179
+ hooks = Hooks()
180
+
181
+
182
+ @tool
183
+ def delete_file(path: str) -> str:
184
+ """Delete a file.
185
+
186
+ Args:
187
+ path: Path of the file to delete.
188
+ """
189
+
190
+ os.remove(path)
191
+ return f"deleted {path}"
192
+
193
+
194
+ @hooks.before_tool
195
+ async def protect_dotfiles(ctx):
196
+ if ctx.tool_name == "delete_file" and str(ctx.tool_input.get("path") or "").startswith("."):
197
+ return ToolExecutionResult(output="error: blocked", is_error=True)
198
+ return None
199
+
200
+
201
+ agent = Agent(
202
+ model="claude-sonnet-4-6",
203
+ api_key="...",
204
+ tools=[delete_file],
205
+ hooks=hooks,
206
+ )
207
+ ```
208
+
209
+ `@hooks.after_tool` runs after the tool and can replace the result (audit, redact, etc.).
210
+
211
+ ## Further reading
212
+
213
+ See [docs/sdk.md](https://github.com/legibet/mycode/blob/main/docs/sdk.md) for the streaming event API, cancellation, retries, compaction, session internals, and the full `Agent` / `@tool` reference.
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["hatchling"]
2
+ requires = ["hatchling>=1.31.0"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "mycode-sdk"
7
- version = "0.9.4"
7
+ version = "0.13.3"
8
8
  description = "Lightweight Python SDK for building AI agents."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -23,11 +23,12 @@ classifiers = [
23
23
  ]
24
24
  keywords = ["agent", "llm", "anthropic", "openai", "gemini", "sdk"]
25
25
  dependencies = [
26
- "anthropic>=0.106.0",
27
- "google-genai>=2.8.0",
28
- "griffelib>=2.0.0",
29
- "openai>=2.41.0",
30
- "pydantic>=2.13.0",
26
+ "anthropic>=0.116.0",
27
+ "google-genai>=2.15.0",
28
+ "griffelib>=2.1.0",
29
+ "httpx>=0.28.0",
30
+ "openai>=2.45.0",
31
+ "pydantic>=2.13.4",
31
32
  ]
32
33
 
33
34
  [project.urls]
@@ -4,12 +4,14 @@ from importlib import metadata
4
4
 
5
5
  from mycode.agent import Agent, Event, PersistCallback, RunResult
6
6
  from mycode.attachments import Attachment
7
+ from mycode.compact import NothingToCompactError
7
8
  from mycode.hooks import AfterToolHook, BeforeToolHook, HookResult, Hooks, ToolHookContext
8
9
  from mycode.messages import (
9
10
  ContentBlock,
10
11
  ConversationMessage,
11
12
  assistant_message,
12
13
  build_message,
14
+ build_usage,
13
15
  document_block,
14
16
  flatten_message_text,
15
17
  image_block,
@@ -19,18 +21,15 @@ from mycode.messages import (
19
21
  tool_use_block,
20
22
  user_text_message,
21
23
  )
24
+ from mycode.models import Cost, ModelMetadata, estimate_cost, resolve_model_metadata
25
+ from mycode.providers.base import ProviderError, StreamStartTimeoutError
22
26
  from mycode.session import SessionStore
23
27
  from mycode.tools import (
24
28
  ToolContext,
25
29
  ToolExecutionResult,
26
30
  ToolExecutor,
27
31
  ToolSpec,
28
- bash_tool,
29
- cancel_all_tools,
30
- edit_tool,
31
- read_tool,
32
32
  tool,
33
- write_tool,
34
33
  )
35
34
 
36
35
  __version__ = metadata.version("mycode-sdk")
@@ -40,14 +39,19 @@ __all__ = [
40
39
  "Attachment",
41
40
  "ContentBlock",
42
41
  "ConversationMessage",
42
+ "Cost",
43
43
  "Event",
44
44
  "AfterToolHook",
45
45
  "BeforeToolHook",
46
46
  "HookResult",
47
47
  "Hooks",
48
+ "ModelMetadata",
49
+ "NothingToCompactError",
48
50
  "PersistCallback",
51
+ "ProviderError",
49
52
  "RunResult",
50
53
  "SessionStore",
54
+ "StreamStartTimeoutError",
51
55
  "ToolContext",
52
56
  "ToolExecutionResult",
53
57
  "ToolExecutor",
@@ -55,19 +59,17 @@ __all__ = [
55
59
  "ToolSpec",
56
60
  "__version__",
57
61
  "assistant_message",
58
- "bash_tool",
59
62
  "build_message",
60
- "cancel_all_tools",
63
+ "build_usage",
61
64
  "document_block",
62
- "edit_tool",
65
+ "estimate_cost",
63
66
  "flatten_message_text",
64
67
  "image_block",
65
- "read_tool",
68
+ "resolve_model_metadata",
66
69
  "text_block",
67
70
  "thinking_block",
68
71
  "tool",
69
72
  "tool_result_block",
70
73
  "tool_use_block",
71
74
  "user_text_message",
72
- "write_tool",
73
75
  ]