grip-sdk 0.3.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.
grip/__init__.py ADDED
@@ -0,0 +1,49 @@
1
+ """Grip: Enterprise AI Search, Dynamic Provider Orchestration, and Multi-Agent SDK.
2
+
3
+ https://grip.theshoaib.me/
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from ._version import __version__
9
+ from .async_client import AsyncClient, AsyncGrip, AsyncGripClient
10
+ from .client import Client, Grip, GripClient
11
+ from .config import ClientConfig, Config, get_config
12
+ from .exceptions import BotBlockedError, ConfigurationError, GripError, ProviderError
13
+ from .types import (
14
+ Metadata,
15
+ SearchResponse,
16
+ SessionDetail,
17
+ SessionMessage,
18
+ SessionSummary,
19
+ SourceItem,
20
+ StreamChunk,
21
+ )
22
+ from grip_engine.infrastructure.cache import TTLCache
23
+ from grip_engine.infrastructure.rate_limit import RateLimiter
24
+
25
+ __all__ = [
26
+ "__version__",
27
+ "Grip",
28
+ "Client",
29
+ "GripClient",
30
+ "AsyncGrip",
31
+ "AsyncClient",
32
+ "AsyncGripClient",
33
+ "Config",
34
+ "ClientConfig",
35
+ "get_config",
36
+ "RateLimiter",
37
+ "TTLCache",
38
+ "SearchResponse",
39
+ "StreamChunk",
40
+ "Metadata",
41
+ "SourceItem",
42
+ "SessionDetail",
43
+ "SessionSummary",
44
+ "SessionMessage",
45
+ "GripError",
46
+ "ProviderError",
47
+ "BotBlockedError",
48
+ "ConfigurationError",
49
+ ]
grip/_version.py ADDED
@@ -0,0 +1,3 @@
1
+ """Package version info."""
2
+
3
+ __version__ = "0.3.0"
grip/async_client.py ADDED
@@ -0,0 +1,161 @@
1
+ """Grip Python SDK Asynchronous Client.
2
+
3
+ Enterprise asynchronous AI Search, Provider Agnostic Orchestration,
4
+ and Multi-turn Conversational Sessions.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ from collections.abc import AsyncGenerator
11
+ from typing import Any
12
+
13
+ from grip_core.config import GripConfig, load_config
14
+ from grip_core.models.response import Metadata, SearchResponse, StreamChunk
15
+ from grip_core.models.session import SessionDetail, SessionSummary
16
+ from grip_core.models.source import SourceItem
17
+
18
+ from .client import Grip
19
+ from .config import ClientConfig
20
+ from .resources.search import AsyncSearchResource
21
+ from .resources.sessions import AsyncSessionsResource
22
+
23
+
24
+ class AsyncGrip:
25
+ """Primary asynchronous client for Grip Platform.
26
+
27
+ Usage:
28
+ >>> import asyncio
29
+ >>> from grip import AsyncGrip
30
+ >>>
31
+ >>> async def main():
32
+ ... client = AsyncGrip()
33
+ ... response = await client.search.query("Quantum computing breakthroughs")
34
+ ... print(response.answer)
35
+ ... await client.close()
36
+ >>> asyncio.run(main())
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ base_url: str | None = None,
42
+ api_key: str | None = None,
43
+ config: GripConfig | ClientConfig | None = None,
44
+ engine: Any = None,
45
+ provider: str = "google",
46
+ timeout: float = 60.0,
47
+ ) -> None:
48
+ self._sync_client = Grip(
49
+ base_url=base_url,
50
+ api_key=api_key,
51
+ config=config,
52
+ engine=engine,
53
+ provider=provider,
54
+ timeout=timeout,
55
+ )
56
+ self.provider = provider
57
+ self.search = AsyncSearchResource(client=self)
58
+ self.sessions = AsyncSessionsResource(client=self)
59
+
60
+ async def _execute_query(
61
+ self,
62
+ query: str,
63
+ session_id: str | None = None,
64
+ provider: str | None = None,
65
+ thread_id: str | None = None,
66
+ timeout: float | None = None,
67
+ ) -> SearchResponse:
68
+ return await asyncio.to_thread(
69
+ self._sync_client._execute_query,
70
+ query=query,
71
+ session_id=session_id,
72
+ provider=provider or self.provider,
73
+ thread_id=thread_id,
74
+ timeout=timeout,
75
+ )
76
+
77
+ async def _execute_stream(
78
+ self,
79
+ query: str,
80
+ session_id: str | None = None,
81
+ provider: str | None = None,
82
+ thread_id: str | None = None,
83
+ ) -> AsyncGenerator[StreamChunk, None]:
84
+ def _get_chunks() -> list[StreamChunk]:
85
+ return list(
86
+ self._sync_client._execute_stream(
87
+ query=query,
88
+ session_id=session_id,
89
+ provider=provider or self.provider,
90
+ thread_id=thread_id,
91
+ )
92
+ )
93
+
94
+ chunks = await asyncio.to_thread(_get_chunks)
95
+ for chunk in chunks:
96
+ yield chunk
97
+
98
+ async def _create_session(
99
+ self,
100
+ title: str | None = None,
101
+ session_id: str | None = None,
102
+ metadata: dict[str, Any] | None = None,
103
+ ) -> SessionDetail:
104
+ return await asyncio.to_thread(
105
+ self._sync_client._create_session,
106
+ title=title,
107
+ session_id=session_id,
108
+ metadata=metadata,
109
+ )
110
+
111
+ async def _get_session(self, session_id: str) -> SessionDetail | None:
112
+ return await asyncio.to_thread(self._sync_client._get_session, session_id)
113
+
114
+ async def _list_sessions(self) -> list[SessionSummary]:
115
+ return await asyncio.to_thread(self._sync_client._list_sessions)
116
+
117
+ async def _delete_session(self, session_id: str) -> bool:
118
+ return await asyncio.to_thread(self._sync_client._delete_session, session_id)
119
+
120
+ async def _clear_sessions(self) -> int:
121
+ return await asyncio.to_thread(self._sync_client._clear_sessions)
122
+
123
+ async def query(
124
+ self,
125
+ query: str,
126
+ session_id: str | None = None,
127
+ provider: str | None = None,
128
+ timeout: float | None = None,
129
+ ) -> SearchResponse:
130
+ return await self.search.query(
131
+ query=query,
132
+ session_id=session_id,
133
+ provider=provider,
134
+ timeout=timeout,
135
+ )
136
+
137
+ async def stream(
138
+ self,
139
+ query: str,
140
+ session_id: str | None = None,
141
+ provider: str | None = None,
142
+ ) -> AsyncGenerator[StreamChunk, None]:
143
+ async for chunk in self.search.stream(
144
+ query=query,
145
+ session_id=session_id,
146
+ provider=provider,
147
+ ):
148
+ yield chunk
149
+
150
+ async def close(self) -> None:
151
+ await asyncio.to_thread(self._sync_client.close)
152
+
153
+ async def __aenter__(self) -> AsyncGrip:
154
+ return self
155
+
156
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
157
+ await self.close()
158
+
159
+
160
+ AsyncClient = AsyncGrip
161
+ AsyncGripClient = AsyncGrip
grip/cli/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Grip CLI package."""
2
+
3
+ from .main import main
4
+
5
+ __all__ = ["main"]
grip/cli/main.py ADDED
@@ -0,0 +1,160 @@
1
+ """Grip CLI: Command-line interface for Grip AI Search & Web Agent."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from typing import Sequence
9
+
10
+ from ..client import Grip
11
+ from .._version import __version__
12
+
13
+
14
+ def main(argv: Sequence[str] | None = None) -> int:
15
+ parser = argparse.ArgumentParser(
16
+ prog="grip",
17
+ description="Grip Platform: Dynamic AI Search & Multi-Agent SDK (https://grip.theshoaib.me/)",
18
+ )
19
+ parser.add_argument("--version", "-v", action="version", version=f"Grip Platform v{__version__}")
20
+ subparsers = parser.add_subparsers(dest="command", help="Sub-commands")
21
+
22
+ # Search sub-command
23
+ search_parser = subparsers.add_parser("search", aliases=["query"], help="Execute an AI search query")
24
+ search_parser.add_argument("query", help="Search query string")
25
+ search_parser.add_argument("--json", action="store_true", help="Output raw JSON response")
26
+ search_parser.add_argument("--session-id", help="Attach query to existing session ID")
27
+ search_parser.add_argument("--provider", default="google", help="Search AI provider (google, openai, etc.)")
28
+ search_parser.add_argument("--no-stream", action="store_true", help="Disable streaming and wait for full response")
29
+
30
+ # Interactive chat sub-command
31
+ chat_parser = subparsers.add_parser("chat", aliases=["session"], help="Start an interactive multi-turn session in terminal")
32
+ chat_parser.add_argument("--title", default="Grip Chat", help="Session title")
33
+ chat_parser.add_argument("--provider", default="google", help="Search AI provider (google, openai, etc.)")
34
+
35
+ # Providers sub-command
36
+ subparsers.add_parser("providers", help="List available AI search providers")
37
+
38
+ # Serve sub-command
39
+ serve_parser = subparsers.add_parser("serve", help="Start the Grip AI web server and UI")
40
+ serve_parser.add_argument("--host", default="0.0.0.0", help="Host interface (default: 0.0.0.0)")
41
+ serve_parser.add_argument("--port", type=int, default=8000, help="Port (default: 8000)")
42
+ serve_parser.add_argument("--debug", action="store_true", help="Enable debug mode")
43
+
44
+ # Rules sub-command
45
+ rules_parser = subparsers.add_parser("rules", help="Manage custom system rules and persona")
46
+ rules_parser.add_argument("--identity", help="Set assistant identity/persona")
47
+ rules_parser.add_argument("--format", help="Set response format specification")
48
+ rules_parser.add_argument("--add", help="Add a behavioral constraint rule")
49
+ rules_parser.add_argument("--list", action="store_true", help="List active rules")
50
+
51
+ args = parser.parse_args(argv)
52
+
53
+ if not args.command:
54
+ parser.print_help()
55
+ return 0
56
+
57
+ if args.command == "providers":
58
+ from grip_engine.providers.registry import default_provider_registry
59
+ providers = default_provider_registry.list_providers()
60
+ print("\nRegistered AI Search Providers:")
61
+ for p in providers:
62
+ print(f"• {p}")
63
+ return 0
64
+
65
+ if args.command == "serve":
66
+ try:
67
+ import uvicorn
68
+ from services.api.app.main import app
69
+ print(f"🚀 Starting Grip AI Platform on http://{args.host}:{args.port}")
70
+ uvicorn.run(app, host=args.host, port=args.port)
71
+ return 0
72
+ except Exception:
73
+ # Fallback to app.server
74
+ from app.server import create_app
75
+ from google_ai_search.config import get_config
76
+ cfg = get_config()
77
+ cfg.port = args.port
78
+ cfg.debug = args.debug
79
+ flask_app = create_app(cfg)
80
+ print(f"🚀 Starting Grip AI Search server on http://{args.host}:{args.port}")
81
+ flask_app.run(host=args.host, port=args.port, debug=args.debug)
82
+ return 0
83
+
84
+ if args.command in ("search", "query"):
85
+ client = Grip(provider=args.provider)
86
+ if args.json:
87
+ res = client.query(args.query, session_id=args.session_id, provider=args.provider)
88
+ print(json.dumps(res.to_dict(), indent=2))
89
+ return 0
90
+
91
+ if args.no_stream:
92
+ res = client.query(args.query, session_id=args.session_id, provider=args.provider)
93
+ print(res.answer)
94
+ return 0
95
+
96
+ try:
97
+ thought_printed = False
98
+ for chunk in client.stream(args.query, session_id=args.session_id, provider=args.provider):
99
+ if chunk.event == "thinking" and chunk.thinking and not thought_printed:
100
+ thought_printed = True
101
+ print(f"💭 [{chunk.thinking}]", flush=True)
102
+ elif chunk.event == "delta" and chunk.delta:
103
+ sys.stdout.write(chunk.delta)
104
+ sys.stdout.flush()
105
+ sys.stdout.write("\n")
106
+ return 0
107
+ except Exception as exc:
108
+ print(f"Error: {exc}", file=sys.stderr)
109
+ return 1
110
+
111
+ if args.command in ("chat", "session"):
112
+ client = Grip(provider=args.provider)
113
+ session = client.create_session(title=args.title)
114
+ print(f"✨ Started Grip interactive session ({session.session_id}) [Provider: {args.provider}]")
115
+ print("Type your questions below. Type 'exit' or 'quit' to end.\n")
116
+
117
+ while True:
118
+ try:
119
+ query = input("\n[You] > ").strip()
120
+ if not query:
121
+ continue
122
+ if query.lower() in ("exit", "quit"):
123
+ print("Goodbye!")
124
+ break
125
+ print("\n[Grip AI] > ", end="", flush=True)
126
+ for chunk in client.stream(query, session_id=session.session_id, provider=args.provider):
127
+ if chunk.event == "delta" and chunk.delta:
128
+ sys.stdout.write(chunk.delta)
129
+ sys.stdout.flush()
130
+ sys.stdout.write("\n")
131
+ except (KeyboardInterrupt, EOFError):
132
+ print("\nSession ended.")
133
+ break
134
+ except Exception as exc:
135
+ print(f"Error: {exc}", file=sys.stderr)
136
+ return 0
137
+
138
+ if args.command == "rules":
139
+ client = Grip()
140
+ if args.identity:
141
+ client.rules.set_identity(args.identity)
142
+ print(f"✅ Set identity: {args.identity}")
143
+ if args.format:
144
+ client.rules.set_response_format(args.format)
145
+ print(f"✅ Set response format: {args.format}")
146
+ if args.add:
147
+ r = client.rules.add(args.add)
148
+ print(f"✅ Added rule: {r.name}")
149
+ if args.list or (not args.identity and not args.format and not args.add):
150
+ active_rules = client.rules.list(enabled_only=True)
151
+ print("\nActive System Rules:")
152
+ for rule in active_rules:
153
+ print(f"• [P{rule.priority}] {rule.name}: {rule.content}")
154
+ return 0
155
+
156
+ return 0
157
+
158
+
159
+ if __name__ == "__main__":
160
+ sys.exit(main())