@passioncode-ai/fabric-agent-adapter 0.6.3 → 0.8.1
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.
- package/CHANGELOG.md +132 -1
- package/COMMERCIAL-LICENSE.md +1 -1
- package/README.md +29 -6
- package/bin/fabric-agent-adapter.js +18 -0
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +9 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +39 -10
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +23 -10
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +77 -14
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +5 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/usage.md +63 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +106 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +77 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +317 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +85 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +472 -15
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +23 -10
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +7 -3
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py
CHANGED
|
@@ -20,9 +20,7 @@ import json
|
|
|
20
20
|
import os
|
|
21
21
|
from http.server import BaseHTTPRequestHandler
|
|
22
22
|
from pathlib import Path
|
|
23
|
-
import signal
|
|
24
23
|
import sys
|
|
25
|
-
import threading
|
|
26
24
|
from typing import Any, Dict, Optional
|
|
27
25
|
from urllib.parse import parse_qs, urlparse
|
|
28
26
|
|
|
@@ -66,12 +64,15 @@ class Service:
|
|
|
66
64
|
self.degraded = [{"source": "demo", "reason": args.degraded}] if args.degraded else []
|
|
67
65
|
self.started_at = fs.now_iso()
|
|
68
66
|
self.build = {"commit": os.environ.get("FABRIC_BUILD_COMMIT", "0000000"), "builtAt": self.started_at}
|
|
67
|
+
self.drain = fs.Drain() # LC-01: SIGTERM drains in-flight calls, then the server stops
|
|
69
68
|
|
|
70
69
|
def start(self) -> None:
|
|
71
70
|
# Rule: the lock comes before ANY side effect, including creating the token.
|
|
72
71
|
self.lock = fs.hold_single_instance(self.data)
|
|
73
72
|
self.token = fs.ensure_token(self.token_file)
|
|
74
73
|
self.log = fs.JsonlEventLog(self.data / "events.jsonl")
|
|
74
|
+
self.usage = fs.JsonlUsageLedger(self.data / "usage.jsonl") # DEC-0021
|
|
75
|
+
self.usage.prune()
|
|
75
76
|
self.codes = fs.LoginCodes(self.data / "auth")
|
|
76
77
|
self.jobs = fi.JobStore(self.data / "jobs")
|
|
77
78
|
self.mcp = fi.McpToolServer(self.id, VERSION, jobs=self.jobs, on_job_started=self.draft_started, on_input=self.draft_answered)
|
|
@@ -117,6 +118,8 @@ class Service:
|
|
|
117
118
|
"binding": {"id": "urn:fabric:binding:sample.draft", "revision": 1, "contentHash": "sha256:" + "0" * 64}, "writeScopes": []},
|
|
118
119
|
not_verified=[{"claim": "NOTE", "reason": "no checker has read the note"}],
|
|
119
120
|
output={"title": title, "body": body}, usage={"inputTokens": 0, "outputTokens": 0, "wallMs": 1}))
|
|
121
|
+
# The sample writes from a template: one call that costs a known $0 (DEC-0021 usage report).
|
|
122
|
+
self.usage.record(fs.make_usage_receipt("local", "sample-template", input_tokens=0, output_tokens=0, cost_usd=0.0, cost_basis="price-list"))
|
|
120
123
|
self.log.append("job.completed", "info", "The note %s is drafted." % title, **fi.trace_ids(ctx.traceparent))
|
|
121
124
|
|
|
122
125
|
def well_known(self) -> Dict[str, Any]:
|
|
@@ -125,7 +128,8 @@ class Service:
|
|
|
125
128
|
started_at=self.started_at, status="ready", degraded=self.degraded,
|
|
126
129
|
summary=[{"label": "Events", "value": len(self.log.fetch(None, fs.EVENTS_MAX_LIMIT))}],
|
|
127
130
|
surfaces={"dashboard": {"path": "/", "login": True}, "events": {"path": "/fabric/v1/events"},
|
|
128
|
-
"mcp": {"path": "/mcp", "transport": "streamable-http", "capabilities": [c["name"] for c in CAPABILITIES]}
|
|
131
|
+
"mcp": {"path": "/mcp", "transport": "streamable-http", "capabilities": [c["name"] for c in CAPABILITIES]},
|
|
132
|
+
"usage": {"path": fs.USAGE_PATH}},
|
|
129
133
|
)
|
|
130
134
|
|
|
131
135
|
|
|
@@ -187,6 +191,10 @@ def make_handler(svc: Service):
|
|
|
187
191
|
except fs.ServiceError as exc:
|
|
188
192
|
return self._send(400, {"error": str(exc)})
|
|
189
193
|
return self._send(200, page)
|
|
194
|
+
if url.path == fs.USAGE_PATH:
|
|
195
|
+
if not self._token_ok():
|
|
196
|
+
return
|
|
197
|
+
return self._send(200, svc.usage.report(service_id=svc.id, instance=svc.instance))
|
|
190
198
|
if url.path == "/mcp":
|
|
191
199
|
# No server-to-client stream here: Streamable HTTP says 405 for a GET.
|
|
192
200
|
return self._send(405, {"error": "POST JSON-RPC to /mcp."}, {"Allow": "POST"})
|
|
@@ -216,7 +224,14 @@ def make_handler(svc: Service):
|
|
|
216
224
|
message = json.loads(self.rfile.read(length) or b"null")
|
|
217
225
|
except ValueError:
|
|
218
226
|
return self._send(400, {"jsonrpc": "2.0", "id": None, "error": {"code": -32700, "message": "Parse error."}})
|
|
219
|
-
|
|
227
|
+
try:
|
|
228
|
+
with svc.drain.work():
|
|
229
|
+
response = svc.mcp.handle(message, self.headers)
|
|
230
|
+
except fs.Stopping as exc:
|
|
231
|
+
return self._send(503, {"jsonrpc": "2.0", "id": None, "error": {"code": -32000, "message": str(exc)}},
|
|
232
|
+
{"Retry-After": "5"})
|
|
233
|
+
if response is not None and (response.get("error") or {}).get("code") == fi.HEADER_MISMATCH:
|
|
234
|
+
return self._send(400, response)
|
|
220
235
|
return self._send(202) if response is None else self._send(200, response)
|
|
221
236
|
if url.path == "/fabric/v1/login-code":
|
|
222
237
|
if not self._token_ok():
|
|
@@ -247,12 +262,10 @@ def serve(args: argparse.Namespace) -> int:
|
|
|
247
262
|
svc.start()
|
|
248
263
|
server = make_server(svc)
|
|
249
264
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
signal.signal(signal.SIGTERM, stop)
|
|
255
|
-
signal.signal(signal.SIGINT, stop)
|
|
265
|
+
# Stop taking calls, let in-flight ones finish (8 s at most), then end serve_forever; a
|
|
266
|
+
# hand-over that hangs is cut by the kit's hard exit, inside launchd's ExitTimeOut.
|
|
267
|
+
svc.drain.install(lambda drained: server.shutdown(),
|
|
268
|
+
on_stopping=lambda: svc.log.append("service.stopping", "info", "%s is stopping." % svc.name))
|
|
256
269
|
try:
|
|
257
270
|
server.serve_forever(poll_interval=0.2)
|
|
258
271
|
finally:
|
|
@@ -5,9 +5,9 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
5
5
|
compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode. Ships in one plugin with adapting-projects-to-fabric, whose scripts it reuses.
|
|
6
6
|
metadata:
|
|
7
7
|
author: PassionCode.ai
|
|
8
|
-
version: "0.
|
|
8
|
+
version: "0.8.1"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
|
-
contract-commit: "
|
|
10
|
+
contract-commit: "623bf61358c339cb10297807b3f024b5d9f1f327"
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Creating Fabric-compatible agents
|
|
@@ -119,12 +119,16 @@ python3 <plugin-dir>/skills/adapting-projects-to-fabric/scripts/adapt_project.py
|
|
|
119
119
|
--schema-base <immutable-base-uri>
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
Pin exactly contract `0.1.0` at commit `
|
|
122
|
+
Pin exactly contract `0.1.0` at commit `623bf61358c339cb10297807b3f024b5d9f1f327` and
|
|
123
123
|
read the pinned guide before implementing protocol details. If this skill is installed
|
|
124
124
|
without its sibling, the scaffolder is absent: create the bundle by hand from the pinned
|
|
125
125
|
contract's `docs/guides/connecting-compatible-agents.md` and mark the structural check
|
|
126
126
|
`NOT_RUN` — do not reconstruct normative rules from memory.
|
|
127
127
|
|
|
128
|
+
This default accepts underscore capability names; a declared communications name does
|
|
129
|
+
not grant COM access or prove runtime admission. Existing bundles retain their explicit
|
|
130
|
+
supported revision and require its exact checkout; never silently migrate their lock.
|
|
131
|
+
|
|
128
132
|
The generated placeholders are deliberate blockers; a bundle still containing one is not
|
|
129
133
|
ready for admission.
|
|
130
134
|
|