agent-as-model 0.1.0__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.
- agent_as_model/__init__.py +3 -0
- agent_as_model/__main__.py +6 -0
- agent_as_model/protocol.py +95 -0
- agent_as_model/server.py +162 -0
- agent_as_model-0.1.0.dist-info/METADATA +130 -0
- agent_as_model-0.1.0.dist-info/RECORD +10 -0
- agent_as_model-0.1.0.dist-info/WHEEL +5 -0
- agent_as_model-0.1.0.dist-info/entry_points.txt +2 -0
- agent_as_model-0.1.0.dist-info/licenses/LICENSE +21 -0
- agent_as_model-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Message translation for agent-as-model.
|
|
2
|
+
|
|
3
|
+
The server never touches a model API key. Every completion is produced by the
|
|
4
|
+
host agent's own LLM through MCP sampling (or the multi-round-trip successor
|
|
5
|
+
on 2026-07-28+ connections). This module translates the two most common
|
|
6
|
+
product API shapes into MCP sampling messages and extracts plain text from
|
|
7
|
+
sampling results.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Literal
|
|
13
|
+
|
|
14
|
+
from mcp.types import CreateMessageResult, SamplingMessage, TextContent
|
|
15
|
+
from pydantic import BaseModel
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ChatMessage(BaseModel):
|
|
19
|
+
"""One OpenAI chat-completions message (text content only, v1)."""
|
|
20
|
+
|
|
21
|
+
role: Literal["user", "assistant", "system"]
|
|
22
|
+
content: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AnthropicContentBlock(BaseModel):
|
|
26
|
+
"""One Anthropic text content block (v1 supports text only)."""
|
|
27
|
+
|
|
28
|
+
type: Literal["text"]
|
|
29
|
+
text: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AnthropicMessage(BaseModel):
|
|
33
|
+
"""One Anthropic messages API message (text only, v1)."""
|
|
34
|
+
|
|
35
|
+
role: Literal["user", "assistant"]
|
|
36
|
+
content: str | list[AnthropicContentBlock]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _text_of(content: str | list[AnthropicContentBlock]) -> str:
|
|
40
|
+
if isinstance(content, str):
|
|
41
|
+
return content
|
|
42
|
+
return "\n".join(block.text for block in content)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def openai_to_sampling(
|
|
46
|
+
messages: list[ChatMessage],
|
|
47
|
+
) -> tuple[str | None, list[SamplingMessage]]:
|
|
48
|
+
"""Split OpenAI chat messages into a system prompt and sampling messages."""
|
|
49
|
+
if not messages:
|
|
50
|
+
raise ValueError("messages must be a non-empty list")
|
|
51
|
+
system_parts: list[str] = []
|
|
52
|
+
sampling: list[SamplingMessage] = []
|
|
53
|
+
for message in messages:
|
|
54
|
+
if message.role == "system":
|
|
55
|
+
system_parts.append(message.content)
|
|
56
|
+
else:
|
|
57
|
+
sampling.append(
|
|
58
|
+
SamplingMessage(
|
|
59
|
+
role=message.role,
|
|
60
|
+
content=TextContent(type="text", text=message.content),
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
system_prompt = "\n\n".join(system_parts) if system_parts else None
|
|
64
|
+
return system_prompt, sampling
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def anthropic_to_sampling(
|
|
68
|
+
messages: list[AnthropicMessage],
|
|
69
|
+
system: str | list[AnthropicContentBlock] | None,
|
|
70
|
+
) -> tuple[str | None, list[SamplingMessage]]:
|
|
71
|
+
"""Split an Anthropic messages request into a system prompt and sampling messages."""
|
|
72
|
+
if not messages:
|
|
73
|
+
raise ValueError("messages must be a non-empty list")
|
|
74
|
+
system_prompt = _text_of(system) if system is not None else None
|
|
75
|
+
sampling = [
|
|
76
|
+
SamplingMessage(
|
|
77
|
+
role=message.role,
|
|
78
|
+
content=TextContent(type="text", text=_text_of(message.content)),
|
|
79
|
+
)
|
|
80
|
+
for message in messages
|
|
81
|
+
]
|
|
82
|
+
return system_prompt, sampling
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def extract_text(result: CreateMessageResult) -> str:
|
|
86
|
+
"""Pull plain text out of a sampling result.
|
|
87
|
+
|
|
88
|
+
Raises ValueError on non-text content instead of silently stringifying it.
|
|
89
|
+
"""
|
|
90
|
+
content = result.content
|
|
91
|
+
if isinstance(content, TextContent):
|
|
92
|
+
return content.text
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"agent-as-model serves text completions only, the host returned {type(content).__name__}"
|
|
95
|
+
)
|
agent_as_model/server.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""agent-as-model: a true MCP server where the agent IS the model and the API key.
|
|
2
|
+
|
|
3
|
+
Install on any MCP host (Claude Code, Cursor, Windsurf, ...). The server
|
|
4
|
+
exposes product-style completion tools; every call is fulfilled by sampling
|
|
5
|
+
the host agent's own LLM. No API keys exist anywhere in this system.
|
|
6
|
+
|
|
7
|
+
Tools:
|
|
8
|
+
chat_complete OpenAI chat-completions-shaped request -> completion text
|
|
9
|
+
messages_create Anthropic messages-shaped request -> completion text
|
|
10
|
+
list_models the model this server offers (the host's own model, aliased)
|
|
11
|
+
|
|
12
|
+
Upstream note: sampling is deprecated as of the 2026-07-28 spec (SEP-2577,
|
|
13
|
+
earliest removal 2027-07-28). This server uses the era-portable
|
|
14
|
+
Resolve/Sample form: a direct sampling request on pre-2026 connections and
|
|
15
|
+
the multi-round-trip pattern on 2026-07-28+. One tool body serves both eras.
|
|
16
|
+
The host must declare the sampling capability or calls fail with a clear
|
|
17
|
+
MISSING_REQUIRED_CLIENT_CAPABILITY error.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
from typing import Annotated
|
|
24
|
+
|
|
25
|
+
from mcp.server.mcpserver import MCPServer, Resolve, Sample
|
|
26
|
+
from mcp.types import (
|
|
27
|
+
CreateMessageResult,
|
|
28
|
+
ModelHint,
|
|
29
|
+
ModelPreferences,
|
|
30
|
+
)
|
|
31
|
+
from pydantic import Field
|
|
32
|
+
|
|
33
|
+
from agent_as_model import __version__
|
|
34
|
+
from agent_as_model.protocol import (
|
|
35
|
+
AnthropicContentBlock,
|
|
36
|
+
AnthropicMessage,
|
|
37
|
+
ChatMessage,
|
|
38
|
+
anthropic_to_sampling,
|
|
39
|
+
extract_text,
|
|
40
|
+
openai_to_sampling,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
mcp = MCPServer(
|
|
44
|
+
name="agent-as-model",
|
|
45
|
+
version=__version__,
|
|
46
|
+
instructions=(
|
|
47
|
+
"Completion tools backed by the host agent's own model. "
|
|
48
|
+
"No API keys are used or needed: the connected agent is the model."
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def _sample_chat(
|
|
54
|
+
messages: list[ChatMessage],
|
|
55
|
+
model: str,
|
|
56
|
+
temperature: float | None,
|
|
57
|
+
max_tokens: int,
|
|
58
|
+
) -> Sample:
|
|
59
|
+
system_prompt, sampling_messages = openai_to_sampling(messages)
|
|
60
|
+
return Sample(
|
|
61
|
+
messages=sampling_messages,
|
|
62
|
+
system_prompt=system_prompt,
|
|
63
|
+
max_tokens=max_tokens,
|
|
64
|
+
temperature=temperature,
|
|
65
|
+
model_preferences=ModelPreferences(hints=[ModelHint(name=model)]),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@mcp.tool(
|
|
70
|
+
description=(
|
|
71
|
+
"Generate a chat completion from an OpenAI chat-completions-shaped request. "
|
|
72
|
+
"The completion is produced by the host agent's own model via MCP sampling. "
|
|
73
|
+
"No API key is used or needed: the connected agent is the model."
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
async def chat_complete(
|
|
77
|
+
completion: Annotated[CreateMessageResult, Resolve(_sample_chat)],
|
|
78
|
+
messages: Annotated[
|
|
79
|
+
list[ChatMessage],
|
|
80
|
+
Field(description="Chat messages; system roles become the system prompt."),
|
|
81
|
+
],
|
|
82
|
+
model: Annotated[
|
|
83
|
+
str, Field(description="Model alias; agent-default means the host's own model.")
|
|
84
|
+
] = "agent-default",
|
|
85
|
+
temperature: Annotated[
|
|
86
|
+
float | None, Field(description="Sampling temperature, if the host honors it.")
|
|
87
|
+
] = None,
|
|
88
|
+
max_tokens: Annotated[int, Field(description="Maximum tokens to generate.")] = 4096,
|
|
89
|
+
) -> str:
|
|
90
|
+
"""Complete a chat with the host agent's model (OpenAI request shape)."""
|
|
91
|
+
return extract_text(completion)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
async def _sample_messages(
|
|
95
|
+
messages: list[AnthropicMessage],
|
|
96
|
+
system: str | list[AnthropicContentBlock] | None,
|
|
97
|
+
model: str,
|
|
98
|
+
temperature: float | None,
|
|
99
|
+
max_tokens: int,
|
|
100
|
+
) -> Sample:
|
|
101
|
+
system_prompt, sampling_messages = anthropic_to_sampling(messages, system)
|
|
102
|
+
return Sample(
|
|
103
|
+
messages=sampling_messages,
|
|
104
|
+
system_prompt=system_prompt,
|
|
105
|
+
max_tokens=max_tokens,
|
|
106
|
+
temperature=temperature,
|
|
107
|
+
model_preferences=ModelPreferences(hints=[ModelHint(name=model)]),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@mcp.tool(
|
|
112
|
+
description=(
|
|
113
|
+
"Generate a completion from an Anthropic messages-shaped request. "
|
|
114
|
+
"The completion is produced by the host agent's own model via MCP sampling. "
|
|
115
|
+
"No API key is used or needed: the connected agent is the model."
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
async def messages_create(
|
|
119
|
+
completion: Annotated[CreateMessageResult, Resolve(_sample_messages)],
|
|
120
|
+
messages: Annotated[
|
|
121
|
+
list[AnthropicMessage], Field(description="User/assistant messages in order.")
|
|
122
|
+
],
|
|
123
|
+
model: Annotated[
|
|
124
|
+
str, Field(description="Model alias; agent-default means the host's own model.")
|
|
125
|
+
] = "agent-default",
|
|
126
|
+
system: Annotated[
|
|
127
|
+
str | list[AnthropicContentBlock] | None,
|
|
128
|
+
Field(description="System prompt as text or text blocks."),
|
|
129
|
+
] = None,
|
|
130
|
+
temperature: Annotated[
|
|
131
|
+
float | None, Field(description="Sampling temperature, if the host honors it.")
|
|
132
|
+
] = None,
|
|
133
|
+
max_tokens: Annotated[int, Field(description="Maximum tokens to generate.")] = 4096,
|
|
134
|
+
) -> str:
|
|
135
|
+
"""Complete a conversation with the host agent's model (Anthropic request shape)."""
|
|
136
|
+
return extract_text(completion)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@mcp.tool(
|
|
140
|
+
description="List the models this server offers. There is exactly one: agent-default, an alias for the host agent's own model."
|
|
141
|
+
)
|
|
142
|
+
async def list_models() -> str:
|
|
143
|
+
"""List available models (the host's own model, aliased)."""
|
|
144
|
+
return json.dumps(
|
|
145
|
+
[
|
|
146
|
+
{
|
|
147
|
+
"id": "agent-default",
|
|
148
|
+
"owned_by": "host-agent",
|
|
149
|
+
"description": (
|
|
150
|
+
"Alias for the MCP host's own model, reached via sampling. No API key required."
|
|
151
|
+
),
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def main() -> None:
|
|
158
|
+
mcp.run_stdio_async()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
main()
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-as-model
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A true MCP server where the agent is the model and the API key.
|
|
5
|
+
Author: Fernando Garza
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/fernandogarzaaa/agent-as-model
|
|
8
|
+
Project-URL: Repository, https://github.com/fernandogarzaaa/agent-as-model
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: mcp<3,>=2
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# agent-as-model
|
|
16
|
+
|
|
17
|
+
A true MCP server where the agent **is** the model and the API key.
|
|
18
|
+
|
|
19
|
+
Install it on any MCP host (Claude Code, Cursor, Windsurf, any spec-compliant
|
|
20
|
+
client). It exposes product-style completion tools. Every call is fulfilled by
|
|
21
|
+
sampling the host agent's own LLM. No API keys exist anywhere in this system.
|
|
22
|
+
|
|
23
|
+
## The idea
|
|
24
|
+
|
|
25
|
+
AI products usually demand a model API key before they do anything useful.
|
|
26
|
+
This server flips that around. The tools speak the request shapes products
|
|
27
|
+
already use, and the intelligence comes from the agent on the other end of
|
|
28
|
+
the MCP connection. The agent is the model. The agent is the key.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
Requires Python 3.10+ and a host that declares the MCP `sampling`
|
|
33
|
+
capability. No API keys, ever.
|
|
34
|
+
|
|
35
|
+
Run it straight from the repo with uvx (no install step):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
claude mcp add agent-as-model -- uvx --from git+https://github.com/fernandogarzaaa/agent-as-model agent-as-model
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or install it for real:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pipx install git+https://github.com/fernandogarzaaa/agent-as-model
|
|
45
|
+
# then
|
|
46
|
+
claude mcp add agent-as-model -- agent-as-model
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Cursor, Windsurf, or any client that takes MCP JSON:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"mcpServers": {
|
|
54
|
+
"agent-as-model": {
|
|
55
|
+
"command": "uvx",
|
|
56
|
+
"args": ["--from", "git+https://github.com/fernandogarzaaa/agent-as-model", "agent-as-model"]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
From a local checkout: `python -m agent_as_model`.
|
|
63
|
+
|
|
64
|
+
## Tools
|
|
65
|
+
|
|
66
|
+
**chat_complete** — OpenAI chat-completions-shaped request, completion text back.
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"messages": [
|
|
71
|
+
{"role": "system", "content": "You are terse."},
|
|
72
|
+
{"role": "user", "content": "Explain MCP sampling."}
|
|
73
|
+
],
|
|
74
|
+
"model": "agent-default",
|
|
75
|
+
"temperature": 0.7,
|
|
76
|
+
"max_tokens": 500
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**messages_create** — Anthropic messages-shaped request, completion text back.
|
|
81
|
+
`system` accepts a string or a list of text blocks. Message content accepts a
|
|
82
|
+
string or a list of text blocks.
|
|
83
|
+
|
|
84
|
+
**list_models** — returns the one model this server offers: `agent-default`,
|
|
85
|
+
an alias for the host agent's own model.
|
|
86
|
+
|
|
87
|
+
## How it works
|
|
88
|
+
|
|
89
|
+
1. A product (or agent) calls `chat_complete` with ordinary chat messages.
|
|
90
|
+
2. The server translates them into MCP sampling messages and asks the host
|
|
91
|
+
to complete them. On pre-2026 connections this is a direct
|
|
92
|
+
`sampling/createMessage` request. On 2026-07-28+ it rides the
|
|
93
|
+
multi-round-trip pattern. One tool body serves both eras.
|
|
94
|
+
3. The host agent generates the completion with its own model and the server
|
|
95
|
+
returns the text. The `model` argument is passed to the host as a model
|
|
96
|
+
hint only; `agent-default` means "your own model".
|
|
97
|
+
|
|
98
|
+
## Honest caveats
|
|
99
|
+
|
|
100
|
+
- Sampling is deprecated upstream (SEP-2577, spec 2026-07-28, earliest
|
|
101
|
+
removal 2027-07-28). The official migration path is provider API keys.
|
|
102
|
+
This project exists because the agent-as-model architecture is the point,
|
|
103
|
+
not the keys. The era-portable form used here is the sanctioned way to
|
|
104
|
+
keep it working across both spec eras.
|
|
105
|
+
- The host is in charge. Sampling runs under the host's consent and policy.
|
|
106
|
+
A host without the sampling capability gets a clear
|
|
107
|
+
`MISSING_REQUIRED_CLIENT_CAPABILITY` error, not a silent failure.
|
|
108
|
+
- Latency is one host model call per tool call. This is a thinking
|
|
109
|
+
endpoint, not a fast one.
|
|
110
|
+
- v1 serves text only. Tool calls, images, and audio in either direction
|
|
111
|
+
are rejected with explicit errors, not silently dropped.
|
|
112
|
+
|
|
113
|
+
## Roadmap
|
|
114
|
+
|
|
115
|
+
- Provider-key fallback for hosts without sampling (opt-in, per-call choice
|
|
116
|
+
stays with the user).
|
|
117
|
+
- Structured output passthrough where hosts support it.
|
|
118
|
+
- More product shapes as tools (embeddings-shaped requests cannot be
|
|
119
|
+
sampled meaningfully; out of scope by design).
|
|
120
|
+
|
|
121
|
+
## Development
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
python3 -m pytest tests/ -q
|
|
125
|
+
python3 -m ruff format . && python3 -m ruff check .
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
14 tests, all green: message translation unit tests plus end-to-end runs of
|
|
129
|
+
the full MCP loop (tool call to sampled completion) against a scripted host
|
|
130
|
+
on both the modern multi-round-trip era and the legacy direct-sampling era.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
agent_as_model/__init__.py,sha256=DkLGJdKQjReesdGnBVoRMaHJYZ_52PboaNNgfdC0SVY,109
|
|
2
|
+
agent_as_model/__main__.py,sha256=GPs1-oXObYgUM_MLZGYf-ejXgGOQ34nc2xkMYaGdDj8,146
|
|
3
|
+
agent_as_model/protocol.py,sha256=iA0u6gofCVSCFIP2SkbytHFYilZHkhdBp8TegB0bv9I,3071
|
|
4
|
+
agent_as_model/server.py,sha256=8y2IrLny7Q6VxOHxCQSy5Du1WGmkvggmeyaVU-hE8iM,5407
|
|
5
|
+
agent_as_model-0.1.0.dist-info/licenses/LICENSE,sha256=VbqRYzR3eEfKB0qZfOnFyBS3eKnLpi_qjzqqkD7o028,1071
|
|
6
|
+
agent_as_model-0.1.0.dist-info/METADATA,sha256=7kCPJMcccCaiHvvqjvz8Z1_NBf8bUrIePqEOZppYX1w,4423
|
|
7
|
+
agent_as_model-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
8
|
+
agent_as_model-0.1.0.dist-info/entry_points.txt,sha256=UWxSor6R970wuPCtSooNp8kCCpMo_olPykJFzeITkX0,62
|
|
9
|
+
agent_as_model-0.1.0.dist-info/top_level.txt,sha256=9TogFQ2wHtPGqeRkYQD9gplJXFGIV8-FaX-j8rCf1gY,15
|
|
10
|
+
agent_as_model-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fernando Garza
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agent_as_model
|