@awiki/cli 0.0.1-beta.2
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/.github/workflows/release.yml +44 -0
- package/.goreleaser.yml +44 -0
- package/AGENTS.md +60 -0
- package/CLAUDE.md +192 -0
- package/README.md +2 -0
- package/docs/architecture/awiki-command-v2.md +955 -0
- package/docs/architecture/awiki-skill-architecture.md +475 -0
- package/docs/architecture/awiki-v2-architecture.md +1063 -0
- package/docs/architecture//345/217/202/350/200/203/346/226/207/346/241/243/cli-init.md +1008 -0
- package/docs/architecture//345/217/202/350/200/203/346/226/207/346/241/243/output-format.md +407 -0
- package/docs/architecture//345/217/202/350/200/203/346/226/207/346/241/243/overall-init.md +741 -0
- package/docs/harness/review-spec.md +474 -0
- package/docs/installation.md +372 -0
- package/docs/plan/awiki-v2-implementation-plan.md +903 -0
- package/docs/plan/phase-0/adr-index.md +56 -0
- package/docs/plan/phase-0/audit-findings.md +251 -0
- package/docs/plan/phase-0/capability-mapping.md +108 -0
- package/docs/plan/phase-0/implementation-constraints.md +363 -0
- package/docs/publish.md +169 -0
- package/go.mod +29 -0
- package/go.sum +73 -0
- package/internal/anpsdk/registry.go +63 -0
- package/internal/authsdk/session.go +351 -0
- package/internal/buildinfo/buildinfo.go +34 -0
- package/internal/cli/app.go +136 -0
- package/internal/cli/app_test.go +88 -0
- package/internal/cli/debug.go +104 -0
- package/internal/cli/group.go +263 -0
- package/internal/cli/id.go +473 -0
- package/internal/cli/init.go +134 -0
- package/internal/cli/msg.go +228 -0
- package/internal/cli/page.go +267 -0
- package/internal/cli/root.go +499 -0
- package/internal/cli/runtime.go +232 -0
- package/internal/cli/upgrade.go +60 -0
- package/internal/cmdmeta/catalog.go +203 -0
- package/internal/cmdmeta/catalog_test.go +21 -0
- package/internal/config/config.go +399 -0
- package/internal/config/config_test.go +104 -0
- package/internal/config/write.go +37 -0
- package/internal/content/service.go +314 -0
- package/internal/content/service_test.go +165 -0
- package/internal/content/types.go +44 -0
- package/internal/docs/topics.go +110 -0
- package/internal/doctor/doctor.go +306 -0
- package/internal/identity/client.go +267 -0
- package/internal/identity/did.go +85 -0
- package/internal/identity/did_test.go +50 -0
- package/internal/identity/layout.go +206 -0
- package/internal/identity/legacy.go +378 -0
- package/internal/identity/public.go +70 -0
- package/internal/identity/public_test.go +73 -0
- package/internal/identity/readiness.go +74 -0
- package/internal/identity/service.go +826 -0
- package/internal/identity/store.go +385 -0
- package/internal/identity/store_test.go +180 -0
- package/internal/identity/types.go +204 -0
- package/internal/message/auth.go +167 -0
- package/internal/message/group_service.go +838 -0
- package/internal/message/group_wire.go +350 -0
- package/internal/message/group_wire_test.go +67 -0
- package/internal/message/helpers.go +61 -0
- package/internal/message/http_client.go +334 -0
- package/internal/message/proof.go +156 -0
- package/internal/message/proof_test.go +61 -0
- package/internal/message/service.go +696 -0
- package/internal/message/service_test.go +97 -0
- package/internal/message/types.go +155 -0
- package/internal/message/wire.go +100 -0
- package/internal/message/wire_test.go +49 -0
- package/internal/message/ws_proxy_client.go +151 -0
- package/internal/output/output.go +350 -0
- package/internal/output/output_test.go +48 -0
- package/internal/runtime/config.go +117 -0
- package/internal/runtime/config_test.go +46 -0
- package/internal/runtime/listener/files.go +65 -0
- package/internal/runtime/listener/manager.go +142 -0
- package/internal/runtime/listener/server.go +983 -0
- package/internal/runtime/listener/server_test.go +319 -0
- package/internal/runtime/listener/sysproc_unix.go +17 -0
- package/internal/runtime/listener/sysproc_windows.go +13 -0
- package/internal/runtime/listener/types.go +21 -0
- package/internal/runtime/listener/wsclient.go +299 -0
- package/internal/runtime/listener/wsclient_test.go +41 -0
- package/internal/store/dao.go +632 -0
- package/internal/store/dao_test.go +87 -0
- package/internal/store/helpers.go +197 -0
- package/internal/store/import.go +499 -0
- package/internal/store/import_test.go +103 -0
- package/internal/store/open.go +71 -0
- package/internal/store/query.go +151 -0
- package/internal/store/schema.go +277 -0
- package/internal/store/schema_test.go +56 -0
- package/internal/store/types.go +177 -0
- package/internal/update/update.go +368 -0
- package/package.json +17 -0
- package/scripts/install.js +171 -0
- package/scripts/release/release-prerelease.sh +86 -0
- package/scripts/release/tag-release.sh +66 -0
- package/scripts/release/withdraw-release.sh +78 -0
- package/scripts/run.js +69 -0
- package/skills/README.md +32 -0
- package/skills/awiki-bundle/SKILL.md +76 -0
- package/skills/awiki-debug/SKILL.md +80 -0
- package/skills/awiki-group/SKILL.md +111 -0
- package/skills/awiki-id/SKILL.md +123 -0
- package/skills/awiki-msg/SKILL.md +131 -0
- package/skills/awiki-page/SKILL.md +93 -0
- package/skills/awiki-people/SKILL.md +66 -0
- package/skills/awiki-runtime/SKILL.md +137 -0
- package/skills/awiki-shared/SKILL.md +124 -0
- package/skills/awiki-workflow-discovery/SKILL.md +93 -0
- package/skills/awiki-workflow-onboarding/SKILL.md +119 -0
- package/skills/manifests/skills.yaml +260 -0
- package/skills/templates/bundle-skill-template.md +42 -0
- package/skills/templates/debug-skill-template.md +44 -0
- package/skills/templates/domain-skill-template.md +56 -0
- package/skills/templates/shared-skill-template.md +46 -0
- package/skills/templates/workflow-skill-template.md +46 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
package message
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"errors"
|
|
7
|
+
"fmt"
|
|
8
|
+
"net/http"
|
|
9
|
+
"strings"
|
|
10
|
+
|
|
11
|
+
"github.com/agentconnect/awiki-cli/internal/authsdk"
|
|
12
|
+
appconfig "github.com/agentconnect/awiki-cli/internal/config"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
type Transport interface {
|
|
16
|
+
SendDirect(context.Context, SendRequest) (*directSendResult, error)
|
|
17
|
+
SendGroup(context.Context, SendRequest) (*groupSendResult, error)
|
|
18
|
+
GetInbox(context.Context, InboxRequest) (map[string]any, error)
|
|
19
|
+
GetHistory(context.Context, HistoryRequest) (map[string]any, error)
|
|
20
|
+
MarkRead(context.Context, MarkReadRequest) (map[string]any, error)
|
|
21
|
+
CreateGroup(context.Context, GroupCreateRequest) (map[string]any, error)
|
|
22
|
+
GetGroupInfo(context.Context, GroupInfoRequest) (map[string]any, error)
|
|
23
|
+
JoinGroup(context.Context, GroupJoinRequest) (map[string]any, error)
|
|
24
|
+
AddGroupMember(context.Context, GroupMemberRequest) (map[string]any, error)
|
|
25
|
+
RemoveGroupMember(context.Context, GroupMemberRequest) (map[string]any, error)
|
|
26
|
+
LeaveGroup(context.Context, GroupLeaveRequest) (map[string]any, error)
|
|
27
|
+
GetGroup(context.Context, GroupGetRequest) (map[string]any, error)
|
|
28
|
+
ListGroupMembers(context.Context, GroupMembersRequest) (map[string]any, error)
|
|
29
|
+
ListGroupMessages(context.Context, GroupMessagesRequest) (map[string]any, error)
|
|
30
|
+
UpdateGroupProfile(context.Context, GroupGetRequest, map[string]any) (map[string]any, error)
|
|
31
|
+
UpdateGroupPolicy(context.Context, GroupGetRequest, map[string]any) (map[string]any, error)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type ServiceError struct {
|
|
35
|
+
StatusCode int
|
|
36
|
+
RPCCode int
|
|
37
|
+
Message string
|
|
38
|
+
Data any
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func (e *ServiceError) Error() string {
|
|
42
|
+
if e == nil {
|
|
43
|
+
return ""
|
|
44
|
+
}
|
|
45
|
+
switch {
|
|
46
|
+
case e.RPCCode != 0:
|
|
47
|
+
return fmt.Sprintf("message service rpc error %d: %s", e.RPCCode, e.Message)
|
|
48
|
+
case e.StatusCode != 0:
|
|
49
|
+
return fmt.Sprintf("message service http error %d: %s", e.StatusCode, e.Message)
|
|
50
|
+
default:
|
|
51
|
+
return e.Message
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type rpcResponse struct {
|
|
56
|
+
Result json.RawMessage `json:"result"`
|
|
57
|
+
Error *struct {
|
|
58
|
+
Code int `json:"code"`
|
|
59
|
+
Message string `json:"message"`
|
|
60
|
+
Data any `json:"data,omitempty"`
|
|
61
|
+
} `json:"error,omitempty"`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type HTTPTransport struct {
|
|
65
|
+
resolved *appconfig.Resolved
|
|
66
|
+
auth *authContext
|
|
67
|
+
httpClient *http.Client
|
|
68
|
+
baseMessageURL string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
func NewHTTPTransport(resolved *appconfig.Resolved, auth *authContext, httpClient *http.Client) *HTTPTransport {
|
|
72
|
+
if httpClient == nil {
|
|
73
|
+
httpClient = http.DefaultClient
|
|
74
|
+
}
|
|
75
|
+
return &HTTPTransport{
|
|
76
|
+
resolved: resolved,
|
|
77
|
+
auth: auth,
|
|
78
|
+
httpClient: httpClient,
|
|
79
|
+
baseMessageURL: strings.TrimRight(resolved.MessageServiceURL, "/"),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func (t *HTTPTransport) SendDirect(ctx context.Context, request SendRequest) (*directSendResult, error) {
|
|
84
|
+
payload, err := buildDirectTextPayload(t.auth.record.DID, request.Target, request.Text, contentTypeForMessageType(request.MessageType))
|
|
85
|
+
if err != nil {
|
|
86
|
+
return nil, err
|
|
87
|
+
}
|
|
88
|
+
senderProof, err := buildSenderProof(t.auth, payload, request.Target)
|
|
89
|
+
if err != nil {
|
|
90
|
+
return nil, err
|
|
91
|
+
}
|
|
92
|
+
params := map[string]any{
|
|
93
|
+
"meta": payload.Meta,
|
|
94
|
+
"auth": map[string]any{
|
|
95
|
+
"scheme": OriginProofScheme,
|
|
96
|
+
"sender_proof": senderProof,
|
|
97
|
+
},
|
|
98
|
+
"body": payload.Body,
|
|
99
|
+
}
|
|
100
|
+
var result directSendResult
|
|
101
|
+
if err := t.rpcCall(ctx, payload.Method, params, &result); err != nil {
|
|
102
|
+
return nil, err
|
|
103
|
+
}
|
|
104
|
+
if result.MessageID == "" {
|
|
105
|
+
result.MessageID = stringFromAny(payload.Meta["message_id"])
|
|
106
|
+
}
|
|
107
|
+
if result.OperationID == "" {
|
|
108
|
+
result.OperationID = stringFromAny(payload.Meta["operation_id"])
|
|
109
|
+
}
|
|
110
|
+
if result.TargetDID == "" {
|
|
111
|
+
result.TargetDID = request.Target
|
|
112
|
+
}
|
|
113
|
+
return &result, nil
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func (t *HTTPTransport) SendGroup(ctx context.Context, request SendRequest) (*groupSendResult, error) {
|
|
117
|
+
params, err := BuildGroupSendRPCParams(t.auth.record, nil, request.Group, request.Text, request.MessageType)
|
|
118
|
+
if err != nil {
|
|
119
|
+
return nil, err
|
|
120
|
+
}
|
|
121
|
+
var result groupSendResult
|
|
122
|
+
if err := t.rpcCall(ctx, "group.send", params, &result); err != nil {
|
|
123
|
+
return nil, err
|
|
124
|
+
}
|
|
125
|
+
if result.GroupDID == "" {
|
|
126
|
+
result.GroupDID = request.Group
|
|
127
|
+
}
|
|
128
|
+
return &result, nil
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
func (t *HTTPTransport) GetInbox(ctx context.Context, request InboxRequest) (map[string]any, error) {
|
|
132
|
+
params := map[string]any{
|
|
133
|
+
"meta": map[string]any{
|
|
134
|
+
"anp_version": "1.0",
|
|
135
|
+
"profile": "anp.inbox.local.v1",
|
|
136
|
+
"security_profile": "transport-protected",
|
|
137
|
+
"sender_did": t.auth.record.DID,
|
|
138
|
+
"operation_id": "op-" + generateOperationID(),
|
|
139
|
+
"created_at": nowRFC3339(),
|
|
140
|
+
},
|
|
141
|
+
"body": map[string]any{
|
|
142
|
+
"user_did": t.auth.record.DID,
|
|
143
|
+
"limit": request.Limit,
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
return t.rpcMapCall(ctx, "inbox.get", params)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func (t *HTTPTransport) GetHistory(ctx context.Context, request HistoryRequest) (map[string]any, error) {
|
|
150
|
+
body := map[string]any{
|
|
151
|
+
"user_did": t.auth.record.DID,
|
|
152
|
+
"peer_did": request.With,
|
|
153
|
+
"limit": request.Limit,
|
|
154
|
+
}
|
|
155
|
+
if strings.TrimSpace(request.Cursor) != "" {
|
|
156
|
+
body["since_seq"] = request.Cursor
|
|
157
|
+
}
|
|
158
|
+
params := map[string]any{
|
|
159
|
+
"meta": map[string]any{
|
|
160
|
+
"anp_version": "1.0",
|
|
161
|
+
"profile": "anp.direct.local.v1",
|
|
162
|
+
"security_profile": "transport-protected",
|
|
163
|
+
"sender_did": t.auth.record.DID,
|
|
164
|
+
"operation_id": "op-" + generateOperationID(),
|
|
165
|
+
"created_at": nowRFC3339(),
|
|
166
|
+
},
|
|
167
|
+
"body": body,
|
|
168
|
+
}
|
|
169
|
+
return t.rpcMapCall(ctx, "direct.get_history", params)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
func (t *HTTPTransport) MarkRead(ctx context.Context, request MarkReadRequest) (map[string]any, error) {
|
|
173
|
+
params := map[string]any{
|
|
174
|
+
"meta": map[string]any{
|
|
175
|
+
"anp_version": "1.0",
|
|
176
|
+
"profile": "anp.inbox.local.v1",
|
|
177
|
+
"security_profile": "transport-protected",
|
|
178
|
+
"sender_did": t.auth.record.DID,
|
|
179
|
+
"operation_id": "op-" + generateOperationID(),
|
|
180
|
+
"created_at": nowRFC3339(),
|
|
181
|
+
},
|
|
182
|
+
"body": map[string]any{
|
|
183
|
+
"user_did": t.auth.record.DID,
|
|
184
|
+
"message_ids": request.MessageIDs,
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
return t.rpcMapCall(ctx, "inbox.mark_read", params)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
func (t *HTTPTransport) CreateGroup(ctx context.Context, request GroupCreateRequest) (map[string]any, error) {
|
|
191
|
+
serviceDID, err := t.GetMessageServiceDID(ctx)
|
|
192
|
+
if err != nil {
|
|
193
|
+
return nil, err
|
|
194
|
+
}
|
|
195
|
+
params, err := BuildGroupCreateRPCParams(t.auth.record, nil, serviceDID, request)
|
|
196
|
+
if err != nil {
|
|
197
|
+
return nil, err
|
|
198
|
+
}
|
|
199
|
+
return t.rpcMapCall(ctx, "group.create", params)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
func (t *HTTPTransport) GetGroupInfo(ctx context.Context, request GroupInfoRequest) (map[string]any, error) {
|
|
203
|
+
params, err := BuildGroupGetInfoRPCParams(t.auth.record, request)
|
|
204
|
+
if err != nil {
|
|
205
|
+
return nil, err
|
|
206
|
+
}
|
|
207
|
+
return t.rpcMapCall(ctx, "group.get_info", params)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
func (t *HTTPTransport) JoinGroup(ctx context.Context, request GroupJoinRequest) (map[string]any, error) {
|
|
211
|
+
params, err := BuildGroupJoinRPCParams(t.auth.record, nil, request)
|
|
212
|
+
if err != nil {
|
|
213
|
+
return nil, err
|
|
214
|
+
}
|
|
215
|
+
return t.rpcMapCall(ctx, "group.join", params)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
func (t *HTTPTransport) AddGroupMember(ctx context.Context, request GroupMemberRequest) (map[string]any, error) {
|
|
219
|
+
params, err := BuildGroupAddRPCParams(t.auth.record, nil, request)
|
|
220
|
+
if err != nil {
|
|
221
|
+
return nil, err
|
|
222
|
+
}
|
|
223
|
+
return t.rpcMapCall(ctx, "group.add", params)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
func (t *HTTPTransport) RemoveGroupMember(ctx context.Context, request GroupMemberRequest) (map[string]any, error) {
|
|
227
|
+
params, err := BuildGroupRemoveRPCParams(t.auth.record, nil, request)
|
|
228
|
+
if err != nil {
|
|
229
|
+
return nil, err
|
|
230
|
+
}
|
|
231
|
+
return t.rpcMapCall(ctx, "group.remove", params)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
func (t *HTTPTransport) LeaveGroup(ctx context.Context, request GroupLeaveRequest) (map[string]any, error) {
|
|
235
|
+
params, err := BuildGroupLeaveRPCParams(t.auth.record, nil, request)
|
|
236
|
+
if err != nil {
|
|
237
|
+
return nil, err
|
|
238
|
+
}
|
|
239
|
+
return t.rpcMapCall(ctx, "group.leave", params)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
func (t *HTTPTransport) GetGroup(ctx context.Context, request GroupGetRequest) (map[string]any, error) {
|
|
243
|
+
params, err := BuildGroupGetRPCParams(t.auth.record, request)
|
|
244
|
+
if err != nil {
|
|
245
|
+
return nil, err
|
|
246
|
+
}
|
|
247
|
+
return t.rpcMapCall(ctx, "group.get", params)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
func (t *HTTPTransport) ListGroupMembers(ctx context.Context, request GroupMembersRequest) (map[string]any, error) {
|
|
251
|
+
params, err := BuildGroupMembersRPCParams(t.auth.record, request)
|
|
252
|
+
if err != nil {
|
|
253
|
+
return nil, err
|
|
254
|
+
}
|
|
255
|
+
return t.rpcMapCall(ctx, "group.list_members", params)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
func (t *HTTPTransport) ListGroupMessages(ctx context.Context, request GroupMessagesRequest) (map[string]any, error) {
|
|
259
|
+
params, err := BuildGroupMessagesRPCParams(t.auth.record, request)
|
|
260
|
+
if err != nil {
|
|
261
|
+
return nil, err
|
|
262
|
+
}
|
|
263
|
+
return t.rpcMapCall(ctx, "group.list_messages", params)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
func (t *HTTPTransport) UpdateGroupProfile(ctx context.Context, request GroupGetRequest, patch map[string]any) (map[string]any, error) {
|
|
267
|
+
params, err := BuildGroupUpdateProfileRPCParams(t.auth.record, nil, request.Group, patch)
|
|
268
|
+
if err != nil {
|
|
269
|
+
return nil, err
|
|
270
|
+
}
|
|
271
|
+
return t.rpcMapCall(ctx, "group.update_profile", params)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
func (t *HTTPTransport) UpdateGroupPolicy(ctx context.Context, request GroupGetRequest, patch map[string]any) (map[string]any, error) {
|
|
275
|
+
params, err := BuildGroupUpdatePolicyRPCParams(t.auth.record, nil, request.Group, patch)
|
|
276
|
+
if err != nil {
|
|
277
|
+
return nil, err
|
|
278
|
+
}
|
|
279
|
+
return t.rpcMapCall(ctx, "group.update_policy", params)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
func (t *HTTPTransport) GetMessageServiceDID(ctx context.Context) (string, error) {
|
|
283
|
+
result, err := t.rpcMapCall(ctx, "anp.get_capabilities", map[string]any{
|
|
284
|
+
"meta": map[string]any{
|
|
285
|
+
"anp_version": "1.0",
|
|
286
|
+
"profile": "anp.core.binding.v1",
|
|
287
|
+
"security_profile": "transport-protected",
|
|
288
|
+
"sender_did": t.auth.record.DID,
|
|
289
|
+
"operation_id": "op-" + generateOperationID(),
|
|
290
|
+
"created_at": nowRFC3339(),
|
|
291
|
+
},
|
|
292
|
+
"body": map[string]any{},
|
|
293
|
+
"client": map[string]any{
|
|
294
|
+
"response_mode": "wait-final",
|
|
295
|
+
},
|
|
296
|
+
})
|
|
297
|
+
if err != nil {
|
|
298
|
+
return "", err
|
|
299
|
+
}
|
|
300
|
+
serviceDID := stringFromAny(result["service_did"])
|
|
301
|
+
if serviceDID == "" {
|
|
302
|
+
return "", fmt.Errorf("message service capabilities response is missing service_did")
|
|
303
|
+
}
|
|
304
|
+
return serviceDID, nil
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
func (t *HTTPTransport) rpcMapCall(ctx context.Context, method string, params map[string]any) (map[string]any, error) {
|
|
308
|
+
var result map[string]any
|
|
309
|
+
if err := t.rpcCall(ctx, method, params, &result); err != nil {
|
|
310
|
+
return nil, err
|
|
311
|
+
}
|
|
312
|
+
if result == nil {
|
|
313
|
+
return map[string]any{}, nil
|
|
314
|
+
}
|
|
315
|
+
return result, nil
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
func (t *HTTPTransport) rpcCall(ctx context.Context, method string, params map[string]any, out any) error {
|
|
319
|
+
requestURL := t.baseMessageURL + MessageRPCEndpoint
|
|
320
|
+
err := t.auth.session.DoJSONRPC(ctx, t.httpClient, requestURL, http.MethodPost, method, params, out)
|
|
321
|
+
if err == nil {
|
|
322
|
+
t.auth.record.JWTToken = t.auth.session.CurrentJWT()
|
|
323
|
+
return nil
|
|
324
|
+
}
|
|
325
|
+
var rpcErr *authsdk.RPCError
|
|
326
|
+
if errors.As(err, &rpcErr) {
|
|
327
|
+
return &ServiceError{RPCCode: rpcErr.Code, Message: rpcErr.Message, Data: rpcErr.Data}
|
|
328
|
+
}
|
|
329
|
+
var httpErr *authsdk.HTTPError
|
|
330
|
+
if errors.As(err, &httpErr) {
|
|
331
|
+
return &ServiceError{StatusCode: httpErr.StatusCode, Message: httpErr.Message}
|
|
332
|
+
}
|
|
333
|
+
return err
|
|
334
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
package message
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"crypto/ecdsa"
|
|
5
|
+
"crypto/rand"
|
|
6
|
+
"crypto/sha256"
|
|
7
|
+
"fmt"
|
|
8
|
+
"math/big"
|
|
9
|
+
"strings"
|
|
10
|
+
"time"
|
|
11
|
+
|
|
12
|
+
"github.com/agentconnect/awiki-cli/internal/anpsdk"
|
|
13
|
+
secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
|
14
|
+
"github.com/google/uuid"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
type directPayload struct {
|
|
18
|
+
Method string `json:"method"`
|
|
19
|
+
Meta map[string]any `json:"meta"`
|
|
20
|
+
Body map[string]any `json:"body"`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type signedPayload = directPayload
|
|
24
|
+
|
|
25
|
+
const OriginProofScheme = "anp-rfc9421-origin-proof-v1"
|
|
26
|
+
|
|
27
|
+
func buildDirectTextPayload(senderDID string, targetDID string, text string, contentType string) (directPayload, error) {
|
|
28
|
+
if senderDID == "" || targetDID == "" {
|
|
29
|
+
return directPayload{}, fmt.Errorf("sender and target did are required")
|
|
30
|
+
}
|
|
31
|
+
if text == "" {
|
|
32
|
+
return directPayload{}, ErrTextRequired
|
|
33
|
+
}
|
|
34
|
+
if contentType == "" {
|
|
35
|
+
contentType = "text/plain"
|
|
36
|
+
}
|
|
37
|
+
operationID := "op-" + uuid.NewString()
|
|
38
|
+
messageID := "msg-" + uuid.NewString()
|
|
39
|
+
meta := map[string]any{
|
|
40
|
+
"anp_version": "1.0",
|
|
41
|
+
"profile": "anp.direct.base.v1",
|
|
42
|
+
"security_profile": "transport-protected",
|
|
43
|
+
"sender_did": senderDID,
|
|
44
|
+
"target": map[string]any{
|
|
45
|
+
"kind": "agent",
|
|
46
|
+
"did": targetDID,
|
|
47
|
+
},
|
|
48
|
+
"operation_id": operationID,
|
|
49
|
+
"message_id": messageID,
|
|
50
|
+
"created_at": time.Now().UTC().Format(time.RFC3339),
|
|
51
|
+
"content_type": contentType,
|
|
52
|
+
}
|
|
53
|
+
body := map[string]any{"text": text}
|
|
54
|
+
return directPayload{Method: "direct.send", Meta: meta, Body: body}, nil
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func buildSenderProof(auth *authContext, payload directPayload, targetDID string) (map[string]any, error) {
|
|
58
|
+
return buildActorProof(auth, signedPayload(payload), "anp://agent/"+strictPercentEncode(targetDID))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func buildActorProof(auth *authContext, payload signedPayload, logicalTargetURI string) (map[string]any, error) {
|
|
62
|
+
keyID := verificationMethodID(auth.record.DIDDocument)
|
|
63
|
+
if keyID == "" {
|
|
64
|
+
return nil, fmt.Errorf("identity %s is missing an authentication verification method", auth.record.IdentityName)
|
|
65
|
+
}
|
|
66
|
+
payloadMap := map[string]any{
|
|
67
|
+
"method": payload.Method,
|
|
68
|
+
"meta": payload.Meta,
|
|
69
|
+
"body": payload.Body,
|
|
70
|
+
}
|
|
71
|
+
canonicalPayload, err := canonicalJSON(payloadMap)
|
|
72
|
+
if err != nil {
|
|
73
|
+
return nil, fmt.Errorf("canonicalize direct payload: %w", err)
|
|
74
|
+
}
|
|
75
|
+
signatureInput, err := anpsdk.BuildIMSignatureInput(keyID, anpsdk.IMGenerationOptions{})
|
|
76
|
+
if err != nil {
|
|
77
|
+
return nil, fmt.Errorf("build actor proof signatureInput: %w", err)
|
|
78
|
+
}
|
|
79
|
+
parsed, err := anpsdk.ParseIMSignatureInput(signatureInput)
|
|
80
|
+
if err != nil {
|
|
81
|
+
return nil, fmt.Errorf("parse actor proof signatureInput: %w", err)
|
|
82
|
+
}
|
|
83
|
+
contentDigest := anpsdk.BuildIMContentDigest(canonicalPayload)
|
|
84
|
+
signatureBase, err := buildBusinessSignatureBase(payload.Method, logicalTargetURI, contentDigest, parsed)
|
|
85
|
+
if err != nil {
|
|
86
|
+
return nil, err
|
|
87
|
+
}
|
|
88
|
+
signatureBytes, err := signBusinessProof(auth.privateKey, []byte(signatureBase))
|
|
89
|
+
if err != nil {
|
|
90
|
+
return nil, fmt.Errorf("sign actor proof: %w", err)
|
|
91
|
+
}
|
|
92
|
+
return map[string]any{
|
|
93
|
+
"contentDigest": contentDigest,
|
|
94
|
+
"signatureInput": signatureInput,
|
|
95
|
+
"signature": anpsdk.EncodeIMSignature(signatureBytes, parsed.Label),
|
|
96
|
+
}, nil
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func strictPercentEncode(value string) string {
|
|
100
|
+
var builder strings.Builder
|
|
101
|
+
builder.Grow(len(value) * 3)
|
|
102
|
+
for i := 0; i < len(value); i++ {
|
|
103
|
+
ch := value[i]
|
|
104
|
+
if (ch >= 'A' && ch <= 'Z') ||
|
|
105
|
+
(ch >= 'a' && ch <= 'z') ||
|
|
106
|
+
(ch >= '0' && ch <= '9') ||
|
|
107
|
+
ch == '-' || ch == '.' || ch == '_' || ch == '~' {
|
|
108
|
+
builder.WriteByte(ch)
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
builder.WriteString(fmt.Sprintf("%%%02X", ch))
|
|
112
|
+
}
|
|
113
|
+
return builder.String()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func signBusinessProof(privateKey anpsdk.PrivateKeyMaterial, message []byte) ([]byte, error) {
|
|
117
|
+
if privateKey.Type != anpsdk.KeyTypeSecp256k1 {
|
|
118
|
+
return nil, fmt.Errorf("unsupported business proof key type: %s", privateKey.Type)
|
|
119
|
+
}
|
|
120
|
+
secpPrivateKey := secp256k1.PrivKeyFromBytes(privateKey.Bytes)
|
|
121
|
+
ecdsaPrivateKey := secpPrivateKey.ToECDSA()
|
|
122
|
+
digest := sha256.Sum256(message)
|
|
123
|
+
r, s, err := ecdsa.Sign(rand.Reader, ecdsaPrivateKey, digest[:])
|
|
124
|
+
if err != nil {
|
|
125
|
+
return nil, err
|
|
126
|
+
}
|
|
127
|
+
curveOrder := secp256k1.S256().Params().N
|
|
128
|
+
halfOrder := new(big.Int).Rsh(new(big.Int).Set(curveOrder), 1)
|
|
129
|
+
if s.Cmp(halfOrder) > 0 {
|
|
130
|
+
s.Sub(curveOrder, s)
|
|
131
|
+
}
|
|
132
|
+
signature := make([]byte, 64)
|
|
133
|
+
copy(signature[32-len(r.Bytes()):32], r.Bytes())
|
|
134
|
+
copy(signature[64-len(s.Bytes()):], s.Bytes())
|
|
135
|
+
return signature, nil
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
func buildBusinessSignatureBase(method string, logicalTargetURI string, contentDigest string, parsed anpsdk.ParsedIMSignatureInput) (string, error) {
|
|
139
|
+
lines := make([]string, 0, len(parsed.Components)+1)
|
|
140
|
+
for _, component := range parsed.Components {
|
|
141
|
+
var value string
|
|
142
|
+
switch component {
|
|
143
|
+
case "@method":
|
|
144
|
+
value = method
|
|
145
|
+
case "@target-uri":
|
|
146
|
+
value = logicalTargetURI
|
|
147
|
+
case "content-digest":
|
|
148
|
+
value = contentDigest
|
|
149
|
+
default:
|
|
150
|
+
return "", fmt.Errorf("unsupported business proof component: %s", component)
|
|
151
|
+
}
|
|
152
|
+
lines = append(lines, fmt.Sprintf("\"%s\": %s", component, value))
|
|
153
|
+
}
|
|
154
|
+
lines = append(lines, fmt.Sprintf("\"@signature-params\": %s", parsed.SignatureParams))
|
|
155
|
+
return strings.Join(lines, "\n"), nil
|
|
156
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package message
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"testing"
|
|
5
|
+
|
|
6
|
+
"github.com/agentconnect/awiki-cli/internal/anpsdk"
|
|
7
|
+
"github.com/agentconnect/awiki-cli/internal/identity"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
func TestBuildSenderProofRoundTrip(t *testing.T) {
|
|
11
|
+
t.Parallel()
|
|
12
|
+
|
|
13
|
+
generated, err := identity.GenerateIdentity(identity.GenerateOptions{
|
|
14
|
+
Hostname: "awiki.ai",
|
|
15
|
+
PathPrefix: []string{"user"},
|
|
16
|
+
ProofDomain: "awiki.ai",
|
|
17
|
+
})
|
|
18
|
+
if err != nil {
|
|
19
|
+
t.Fatalf("GenerateIdentity() error = %v", err)
|
|
20
|
+
}
|
|
21
|
+
record := &identity.StoredIdentity{
|
|
22
|
+
IdentityName: "default",
|
|
23
|
+
DID: generated.DID,
|
|
24
|
+
DIDDocument: generated.DIDDocument,
|
|
25
|
+
Key1PrivatePEM: generated.Key1PrivatePEM,
|
|
26
|
+
}
|
|
27
|
+
auth, err := newAuthContext(record, nil)
|
|
28
|
+
if err != nil {
|
|
29
|
+
t.Fatalf("newAuthContext() error = %v", err)
|
|
30
|
+
}
|
|
31
|
+
payload, err := buildDirectTextPayload(generated.DID, "did:wba:awiki.ai:user:bob", "hello", "text/plain")
|
|
32
|
+
if err != nil {
|
|
33
|
+
t.Fatalf("buildDirectTextPayload() error = %v", err)
|
|
34
|
+
}
|
|
35
|
+
proofMap, err := buildSenderProof(auth, payload, "did:wba:awiki.ai:user:bob")
|
|
36
|
+
if err != nil {
|
|
37
|
+
t.Fatalf("buildSenderProof() error = %v", err)
|
|
38
|
+
}
|
|
39
|
+
signatureInput, _ := proofMap["signatureInput"].(string)
|
|
40
|
+
parsed, err := anpsdk.ParseIMSignatureInput(signatureInput)
|
|
41
|
+
if err != nil {
|
|
42
|
+
t.Fatalf("ParseIMSignatureInput() error = %v", err)
|
|
43
|
+
}
|
|
44
|
+
payloadMap := map[string]any{"method": payload.Method, "meta": payload.Meta, "body": payload.Body}
|
|
45
|
+
canonicalPayload, err := canonicalJSON(payloadMap)
|
|
46
|
+
if err != nil {
|
|
47
|
+
t.Fatalf("canonicalJSON() error = %v", err)
|
|
48
|
+
}
|
|
49
|
+
signatureBase, err := buildBusinessSignatureBase(payload.Method, "anp://agent/"+strictPercentEncode("did:wba:awiki.ai:user:bob"), proofMap["contentDigest"].(string), parsed)
|
|
50
|
+
if err != nil {
|
|
51
|
+
t.Fatalf("buildBusinessSignatureBase() error = %v", err)
|
|
52
|
+
}
|
|
53
|
+
proof := anpsdk.IMProof{
|
|
54
|
+
ContentDigest: proofMap["contentDigest"].(string),
|
|
55
|
+
SignatureInput: signatureInput,
|
|
56
|
+
Signature: proofMap["signature"].(string),
|
|
57
|
+
}
|
|
58
|
+
if _, err := anpsdk.VerifyIMProofWithDocument(proof, canonicalPayload, []byte(signatureBase), generated.DIDDocument, generated.DID); err != nil {
|
|
59
|
+
t.Fatalf("VerifyIMProofWithDocument() error = %v", err)
|
|
60
|
+
}
|
|
61
|
+
}
|