ankka 0.9.1 → 0.11.0

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 (78) hide show
  1. package/dist/_proto/ankka/protocol/v1/agent_pb.d.ts +35 -0
  2. package/dist/_proto/ankka/protocol/v1/agent_pb.js +8 -1
  3. package/dist/_proto/ankka/protocol/v1/client_pb.d.ts +542 -2
  4. package/dist/_proto/ankka/protocol/v1/client_pb.js +107 -6
  5. package/dist/_proto/ankka/protocol/v1/consumer_pb.d.ts +52 -1
  6. package/dist/_proto/ankka/protocol/v1/consumer_pb.js +11 -1
  7. package/dist/_proto/ankka/protocol/v1/discovery_pb.d.ts +330 -0
  8. package/dist/_proto/ankka/protocol/v1/discovery_pb.js +81 -16
  9. package/dist/_proto/ankka/protocol/v1/endpoint_pb.d.ts +184 -0
  10. package/dist/_proto/ankka/protocol/v1/endpoint_pb.js +26 -1
  11. package/dist/_proto/ankka/protocol/v1/view_pb.d.ts +63 -0
  12. package/dist/_proto/ankka/protocol/v1/view_pb.js +11 -1
  13. package/dist/_proto/ankka/protocol/v1/workflow_pb.d.ts +7 -0
  14. package/dist/_proto/ankka/protocol/v1/workflow_pb.js +1 -1
  15. package/dist/agent.d.ts +15 -1
  16. package/dist/agent.js +20 -0
  17. package/dist/approvals.d.ts +47 -0
  18. package/dist/approvals.js +57 -0
  19. package/dist/autonomous.d.ts +23 -1
  20. package/dist/autonomous.js +30 -1
  21. package/dist/client.d.ts +92 -4
  22. package/dist/client.js +262 -15
  23. package/dist/consumer.d.ts +40 -2
  24. package/dist/consumer.js +50 -0
  25. package/dist/context.d.ts +4 -0
  26. package/dist/contract.d.ts +36 -0
  27. package/dist/contract.js +58 -0
  28. package/dist/effects/keyed.d.ts +33 -0
  29. package/dist/effects/keyed.js +41 -0
  30. package/dist/effects/stateless.d.ts +21 -0
  31. package/dist/effects/stateless.js +19 -0
  32. package/dist/endpoint.d.ts +10 -1
  33. package/dist/endpoint.js +20 -0
  34. package/dist/graph.d.ts +171 -0
  35. package/dist/graph.js +446 -0
  36. package/dist/handlers.d.ts +10 -2
  37. package/dist/handlers.js +6 -2
  38. package/dist/index.d.ts +15 -6
  39. package/dist/index.js +13 -4
  40. package/dist/keyedView.d.ts +56 -0
  41. package/dist/keyedView.js +67 -0
  42. package/dist/mcp.d.ts +57 -0
  43. package/dist/mcp.js +64 -0
  44. package/dist/routes.d.ts +23 -3
  45. package/dist/routes.js +16 -3
  46. package/dist/server/agent.js +12 -4
  47. package/dist/server/discovery.d.ts +12 -1
  48. package/dist/server/discovery.js +95 -1
  49. package/dist/server/http.d.ts +2 -1
  50. package/dist/server/http.js +78 -4
  51. package/dist/server/stateless.js +104 -16
  52. package/dist/server/workflow.js +5 -1
  53. package/dist/service.d.ts +45 -3
  54. package/dist/service.js +234 -12
  55. package/dist/services.d.ts +137 -0
  56. package/dist/services.js +259 -0
  57. package/dist/socket.d.ts +15 -0
  58. package/dist/socket.js +25 -0
  59. package/dist/spec.d.ts +1 -1
  60. package/dist/spec.js +87 -14
  61. package/dist/startFrom.d.ts +32 -0
  62. package/dist/startFrom.js +44 -0
  63. package/dist/testkit/index.d.ts +2 -2
  64. package/dist/testkit/index.js +1 -1
  65. package/dist/testkit/integration.js +5 -0
  66. package/dist/testkit/kinds.d.ts +75 -5
  67. package/dist/testkit/kinds.js +264 -34
  68. package/dist/testkit/unit.d.ts +14 -1
  69. package/dist/testkit/unit.js +40 -3
  70. package/dist/timedAction.d.ts +17 -2
  71. package/dist/timedAction.js +33 -1
  72. package/dist/version.d.ts +1 -1
  73. package/dist/version.js +1 -1
  74. package/dist/view.d.ts +32 -0
  75. package/dist/view.js +8 -0
  76. package/dist/workflow.d.ts +18 -1
  77. package/dist/workflow.js +39 -0
  78. package/package.json +3 -2
@@ -0,0 +1,137 @@
1
+ import { type ComponentClient } from "./client.ts";
2
+ import { type Shape } from "./codec.ts";
3
+ /** The protocol that added calls to other services. */
4
+ export declare const SERVICES_SINCE = "1.8";
5
+ /** The largest body, either way, a call through the sidecar carries: under gRPC's 4 MiB. */
6
+ export declare const MAX_BODY_BYTES = 4000000;
7
+ /** Headers, in order; a name may appear twice. The platform's own are removed before sending. */
8
+ export type ServiceHeaders = ReadonlyArray<readonly [string, string]>;
9
+ /** What the service answered: its status, content type, body and headers, as it sent them. */
10
+ export interface ServiceResponse {
11
+ readonly status: number;
12
+ readonly contentType: string;
13
+ readonly body: Uint8Array;
14
+ readonly headers: ServiceHeaders;
15
+ readonly text: string;
16
+ }
17
+ /** A call to another service that did not end in an answer a typed helper accepts. */
18
+ export declare class ServiceError extends Error {
19
+ readonly service: string;
20
+ constructor(service: string, message: string);
21
+ }
22
+ /** No such service was found; nothing was sent. `reason` says what was tried. */
23
+ export declare class ServiceUnresolvable extends ServiceError {
24
+ readonly reason: string;
25
+ constructor(service: string, reason: string);
26
+ }
27
+ /** What answered for the name is not the service asked for; nothing was sent. */
28
+ export declare class ServiceIdentityMismatch extends ServiceError {
29
+ readonly detail: string;
30
+ constructor(service: string, detail: string);
31
+ }
32
+ /**
33
+ * No answer came: the connection was refused or broke, or the service's timeout passed. The service
34
+ * may have received the request; one that may change something was sent at most once.
35
+ */
36
+ export declare class ServiceUnanswered extends ServiceError {
37
+ readonly reason: string;
38
+ constructor(service: string, reason: string);
39
+ }
40
+ /** The service answered with a status outside 2xx, to a typed helper. `request` returns every answer. */
41
+ export declare class ServiceCallFailed extends ServiceError {
42
+ readonly status: number;
43
+ readonly body: string;
44
+ constructor(service: string, status: number, body: string);
45
+ }
46
+ /** One request, as a client sends it. */
47
+ export interface ServiceRequest {
48
+ readonly service: string;
49
+ readonly project: string | undefined;
50
+ readonly method: string;
51
+ readonly path: string;
52
+ readonly headers: ServiceHeaders;
53
+ readonly contentType: string | undefined;
54
+ readonly body: Uint8Array | undefined;
55
+ }
56
+ type Send = (request: ServiceRequest, target: string) => Promise<ServiceResponse>;
57
+ /** Calls one service. `target` is the service as it was named: `name` or `project/name`. */
58
+ export declare class ServiceClient {
59
+ #private;
60
+ readonly target: string;
61
+ /** @internal */
62
+ constructor(send: Send, service: string, project: string | undefined);
63
+ /** One request, answered whatever its status. Throws only when no answer came. */
64
+ request(method: string, path: string, options?: {
65
+ body?: Uint8Array;
66
+ contentType?: string;
67
+ headers?: ServiceHeaders;
68
+ }): Promise<ServiceResponse>;
69
+ /** A JSON answer, decoded as `returns`; a status outside 2xx is a `ServiceCallFailed`. */
70
+ get<R>(path: string, returns: Shape<R>, options?: {
71
+ headers?: ServiceHeaders;
72
+ }): Promise<R>;
73
+ /** A text answer: what an endpoint returning a string sends. */
74
+ getText(path: string, options?: {
75
+ headers?: ServiceHeaders;
76
+ }): Promise<string>;
77
+ post<B, R>(path: string, body: B, shapes: {
78
+ body: Shape<B>;
79
+ returns: Shape<R>;
80
+ }, options?: {
81
+ headers?: ServiceHeaders;
82
+ }): Promise<R>;
83
+ put<B, R>(path: string, body: B, shapes: {
84
+ body: Shape<B>;
85
+ returns: Shape<R>;
86
+ }, options?: {
87
+ headers?: ServiceHeaders;
88
+ }): Promise<R>;
89
+ /** For a route answering nothing: succeeds on any 2xx. */
90
+ delete(path: string, options?: {
91
+ headers?: ServiceHeaders;
92
+ }): Promise<void>;
93
+ }
94
+ /**
95
+ * How a component obtains clients for other services: `this.services.service("orders")`, or
96
+ * `this.services.service("billing", "invoices")` for a service in another project — whether it admits
97
+ * this one is its own ACL's decision. The call carries the metadata of the handler that is running, so
98
+ * the runtime makes it as that handler: counted from it, and in its trace.
99
+ */
100
+ export declare class Services {
101
+ #private;
102
+ /** Calls through `client`'s sidecar, as the handler `client` is scoped to; with none, every call throws. */
103
+ constructor(client?: ComponentClient);
104
+ service(name: string): ServiceClient;
105
+ service(project: string, name: string): ServiceClient;
106
+ /** @internal */
107
+ _send(request: ServiceRequest, target: string): Promise<ServiceResponse>;
108
+ }
109
+ /**
110
+ * Other services, for a unit test: each answers as the test scripted it, and every request is
111
+ * recorded. A call to a service nothing is scripted for fails the test, naming the service: a test
112
+ * whose call quietly got a default answer is no longer testing what it says.
113
+ */
114
+ export declare class ScriptedServices extends Services {
115
+ #private;
116
+ readonly requests: ServiceRequest[];
117
+ constructor();
118
+ /** `name` answers with what `handler` returns; in this service's own project unless one is given. */
119
+ answer(name: string, handler: (request: ServiceRequest) => ServiceResponse, project?: string): this;
120
+ unresolvable(name: string, project?: string): this;
121
+ unanswered(name: string, project?: string): this;
122
+ mismatch(name: string, project?: string): this;
123
+ /** An answer of `body` as `text/plain`, for a script. */
124
+ static text(body: string, status?: number): ServiceResponse;
125
+ /** An answer of `body` as `application/json`, for a script. */
126
+ static json(body: string, status?: number): ServiceResponse;
127
+ /** @internal */
128
+ _send(request: ServiceRequest, target: string): Promise<ServiceResponse>;
129
+ }
130
+ /** Services for a unit test whose every call throws: the unit kit's default, beside `noClient()`. */
131
+ export declare function noServices(): Services;
132
+ /**
133
+ * @internal Other services, reached through `client`'s sidecar as the handler it is scoped to. Not a
134
+ * member of the client, which an entity holds too: only the kinds that may call one call this.
135
+ */
136
+ export declare function servicesFor(client: ComponentClient): Services;
137
+ export {};
@@ -0,0 +1,259 @@
1
+ // Other services, called as this one. The runtime beside this process makes the call: it finds the
2
+ // service, presents this service's certificate and checks that what answered is the service asked
3
+ // for. This process never holds a key. Offered to endpoints, workflow steps, consumers, timed actions
4
+ // and agents; an entity and a view have no `services` at all.
5
+ import { Code, ConnectError } from "@connectrpc/connect";
6
+ import { ServiceFailure_Reason } from "./_proto/ankka/protocol/v1/client_pb.js";
7
+ import { channelOf } from "./client.js";
8
+ import { codecFor } from "./codec.js";
9
+ import { CommandError } from "./effects/common.js";
10
+ import { errorCodeFromProto } from "./kinds.js";
11
+ import { metadataToProto } from "./context.js";
12
+ import { PROTOCOL_VERSION } from "./spec.js";
13
+ /** The protocol that added calls to other services. */
14
+ export const SERVICES_SINCE = "1.8";
15
+ /** The largest body, either way, a call through the sidecar carries: under gRPC's 4 MiB. */
16
+ export const MAX_BODY_BYTES = 4_000_000;
17
+ /** A call to another service that did not end in an answer a typed helper accepts. */
18
+ export class ServiceError extends Error {
19
+ service;
20
+ constructor(service, message) {
21
+ super(message);
22
+ this.name = "ServiceError";
23
+ this.service = service;
24
+ }
25
+ }
26
+ /** No such service was found; nothing was sent. `reason` says what was tried. */
27
+ export class ServiceUnresolvable extends ServiceError {
28
+ reason;
29
+ constructor(service, reason) {
30
+ super(service, `cannot reach ${service}: ${reason}`);
31
+ this.name = "ServiceUnresolvable";
32
+ this.reason = reason;
33
+ }
34
+ }
35
+ /** What answered for the name is not the service asked for; nothing was sent. */
36
+ export class ServiceIdentityMismatch extends ServiceError {
37
+ detail;
38
+ constructor(service, detail) {
39
+ super(service, `the service reached as ${service} is not it: ${detail}`);
40
+ this.name = "ServiceIdentityMismatch";
41
+ this.detail = detail;
42
+ }
43
+ }
44
+ /**
45
+ * No answer came: the connection was refused or broke, or the service's timeout passed. The service
46
+ * may have received the request; one that may change something was sent at most once.
47
+ */
48
+ export class ServiceUnanswered extends ServiceError {
49
+ reason;
50
+ constructor(service, reason) {
51
+ super(service, `${service} did not answer: ${reason}`);
52
+ this.name = "ServiceUnanswered";
53
+ this.reason = reason;
54
+ }
55
+ }
56
+ /** The service answered with a status outside 2xx, to a typed helper. `request` returns every answer. */
57
+ export class ServiceCallFailed extends ServiceError {
58
+ status;
59
+ body;
60
+ constructor(service, status, body) {
61
+ super(service, `${service} answered ${status}: ${body}`);
62
+ this.name = "ServiceCallFailed";
63
+ this.status = status;
64
+ this.body = body;
65
+ }
66
+ }
67
+ const decoder = new TextDecoder();
68
+ function response(status, contentType, body, headers) {
69
+ return { status, contentType, body, headers, get text() { return decoder.decode(body); } };
70
+ }
71
+ /** Calls one service. `target` is the service as it was named: `name` or `project/name`. */
72
+ export class ServiceClient {
73
+ target;
74
+ #send;
75
+ #service;
76
+ #project;
77
+ /** @internal */
78
+ constructor(send, service, project) {
79
+ this.#send = send;
80
+ this.#service = service;
81
+ this.#project = project;
82
+ this.target = project ? `${project}/${service}` : service;
83
+ }
84
+ /** One request, answered whatever its status. Throws only when no answer came. */
85
+ async request(method, path, options = {}) {
86
+ if (options.body && options.body.length > MAX_BODY_BYTES) {
87
+ throw new CommandError({
88
+ message: `a call's body is at most ${MAX_BODY_BYTES} bytes through the sidecar; this one is ${options.body.length}`,
89
+ code: "BAD_REQUEST",
90
+ });
91
+ }
92
+ return this.#send({
93
+ service: this.#service,
94
+ project: this.#project,
95
+ method,
96
+ path,
97
+ headers: options.headers ?? [],
98
+ contentType: options.contentType,
99
+ body: options.body,
100
+ }, this.target);
101
+ }
102
+ /** A JSON answer, decoded as `returns`; a status outside 2xx is a `ServiceCallFailed`. */
103
+ async get(path, returns, options = {}) {
104
+ return codecFor(returns).decode(this.#succeeded(await this.request("GET", path, options)).body);
105
+ }
106
+ /** A text answer: what an endpoint returning a string sends. */
107
+ async getText(path, options = {}) {
108
+ return this.#succeeded(await this.request("GET", path, options)).text;
109
+ }
110
+ async post(path, body, shapes, options = {}) {
111
+ return this.#withBody("POST", path, body, shapes, options);
112
+ }
113
+ async put(path, body, shapes, options = {}) {
114
+ return this.#withBody("PUT", path, body, shapes, options);
115
+ }
116
+ /** For a route answering nothing: succeeds on any 2xx. */
117
+ async delete(path, options = {}) {
118
+ this.#succeeded(await this.request("DELETE", path, options));
119
+ }
120
+ async #withBody(method, path, body, shapes, options) {
121
+ const codec = codecFor(shapes.body);
122
+ const answer = await this.request(method, path, { body: codec.encode(body), contentType: codec.contentType, headers: options.headers });
123
+ return codecFor(shapes.returns).decode(this.#succeeded(answer).body);
124
+ }
125
+ #succeeded(answer) {
126
+ if (Math.floor(answer.status / 100) !== 2)
127
+ throw new ServiceCallFailed(this.target, answer.status, answer.text);
128
+ return answer;
129
+ }
130
+ }
131
+ /**
132
+ * How a component obtains clients for other services: `this.services.service("orders")`, or
133
+ * `this.services.service("billing", "invoices")` for a service in another project — whether it admits
134
+ * this one is its own ACL's decision. The call carries the metadata of the handler that is running, so
135
+ * the runtime makes it as that handler: counted from it, and in its trace.
136
+ */
137
+ export class Services {
138
+ #client;
139
+ /** Calls through `client`'s sidecar, as the handler `client` is scoped to; with none, every call throws. */
140
+ constructor(client) {
141
+ this.#client = client;
142
+ }
143
+ service(first, second) {
144
+ const send = (request, target) => this._send(request, target);
145
+ return second === undefined ? new ServiceClient(send, first, undefined) : new ServiceClient(send, second, first);
146
+ }
147
+ /** @internal */
148
+ async _send(request, target) {
149
+ const channel = this.#client ? channelOf(this.#client) : undefined;
150
+ if (!channel)
151
+ throw new Error("these services are not connected to a runtime; a unit test passes a ScriptedServices");
152
+ const reply = await channel
153
+ .stub()
154
+ .request({
155
+ service: request.service,
156
+ project: request.project,
157
+ method: request.method,
158
+ path: request.path,
159
+ headers: request.headers.map(([name, value]) => ({ name, value })),
160
+ contentType: request.contentType,
161
+ body: request.body,
162
+ metadata: metadataToProto(channel.metadata),
163
+ })
164
+ .catch(tooOld);
165
+ return answerOf(target, reply);
166
+ }
167
+ }
168
+ function tooOld(failure) {
169
+ if (failure instanceof ConnectError && failure.code === Code.Unimplemented) {
170
+ throw new CommandError({
171
+ message: `the runtime beside this process cannot call another service, which needs protocol ${SERVICES_SINCE} (this SDK speaks ${PROTOCOL_VERSION}): ${failure.rawMessage}`,
172
+ code: "INTERNAL",
173
+ });
174
+ }
175
+ throw failure;
176
+ }
177
+ function answerOf(target, reply) {
178
+ switch (reply.result.case) {
179
+ case "response": {
180
+ const r = reply.result.value;
181
+ return response(r.status, r.contentType, r.body, r.headers.map((h) => [h.name, h.value]));
182
+ }
183
+ case "failure": {
184
+ const { reason, detail } = reply.result.value;
185
+ if (reason === ServiceFailure_Reason.UNRESOLVABLE)
186
+ throw new ServiceUnresolvable(target, detail);
187
+ if (reason === ServiceFailure_Reason.IDENTITY_MISMATCH)
188
+ throw new ServiceIdentityMismatch(target, detail);
189
+ throw new ServiceUnanswered(target, detail);
190
+ }
191
+ case "error":
192
+ throw new CommandError({ message: reply.result.value.message, code: errorCodeFromProto(reply.result.value.code) });
193
+ default:
194
+ throw new CommandError({ message: `the runtime answered a call to ${target} with nothing`, code: "INTERNAL" });
195
+ }
196
+ }
197
+ /**
198
+ * Other services, for a unit test: each answers as the test scripted it, and every request is
199
+ * recorded. A call to a service nothing is scripted for fails the test, naming the service: a test
200
+ * whose call quietly got a default answer is no longer testing what it says.
201
+ */
202
+ export class ScriptedServices extends Services {
203
+ requests = [];
204
+ #scripts = new Map();
205
+ constructor() {
206
+ super(undefined);
207
+ }
208
+ /** `name` answers with what `handler` returns; in this service's own project unless one is given. */
209
+ answer(name, handler, project) {
210
+ this.#scripts.set(keyOf(project, name), handler);
211
+ return this;
212
+ }
213
+ unresolvable(name, project) {
214
+ this.#scripts.set(keyOf(project, name), new ServiceUnresolvable(keyOf(project, name), "scripted as unresolvable"));
215
+ return this;
216
+ }
217
+ unanswered(name, project) {
218
+ this.#scripts.set(keyOf(project, name), new ServiceUnanswered(keyOf(project, name), "scripted as unanswered"));
219
+ return this;
220
+ }
221
+ mismatch(name, project) {
222
+ this.#scripts.set(keyOf(project, name), new ServiceIdentityMismatch(keyOf(project, name), "scripted as another service"));
223
+ return this;
224
+ }
225
+ /** An answer of `body` as `text/plain`, for a script. */
226
+ static text(body, status = 200) {
227
+ return response(status, "text/plain", new TextEncoder().encode(body), []);
228
+ }
229
+ /** An answer of `body` as `application/json`, for a script. */
230
+ static json(body, status = 200) {
231
+ return response(status, "application/json", new TextEncoder().encode(body), []);
232
+ }
233
+ /** @internal */
234
+ async _send(request, target) {
235
+ this.requests.push(request);
236
+ const script = this.#scripts.get(keyOf(request.project, request.service));
237
+ if (script === undefined) {
238
+ const project = request.project ? `, "${request.project}"` : "";
239
+ throw new Error(`the test called the service '${target}', and nothing is scripted for it: script it with answer("${request.service}", (request) => ScriptedServices.text(...)${project})`);
240
+ }
241
+ if (script instanceof ServiceError)
242
+ throw script;
243
+ return script(request);
244
+ }
245
+ }
246
+ function keyOf(project, name) {
247
+ return project ? `${project}/${name}` : name;
248
+ }
249
+ /** Services for a unit test whose every call throws: the unit kit's default, beside `noClient()`. */
250
+ export function noServices() {
251
+ return new Services(undefined);
252
+ }
253
+ /**
254
+ * @internal Other services, reached through `client`'s sidecar as the handler it is scoped to. Not a
255
+ * member of the client, which an entity holds too: only the kinds that may call one call this.
256
+ */
257
+ export function servicesFor(client) {
258
+ return new Services(client);
259
+ }
@@ -0,0 +1,15 @@
1
+ /** Thrown by `Socket.send` once the socket is closed. A handler that lets it escape has ended as a
2
+ * handler ends when its client goes: it is not a failure. */
3
+ export declare class SocketClosed extends Error {
4
+ constructor(message?: string);
5
+ }
6
+ /** A socket route handler's hold on its socket: `for await (const text of socket)` reads frames until
7
+ * it is closed, and `await socket.send(text)` writes one. */
8
+ export interface Socket extends AsyncIterable<string> {
9
+ /** The next frame, or `undefined` once the socket is closed. */
10
+ receive(): Promise<string | undefined>;
11
+ /** Sends a frame; rejects with `SocketClosed` once the socket is closed. */
12
+ send(text: string): Promise<void>;
13
+ }
14
+ /** A socket over two functions: what the servicer and the endpoint test kit each give a handler. */
15
+ export declare function socketOf(receive: () => Promise<string | undefined>, send: (text: string) => Promise<void>): Socket;
package/dist/socket.js ADDED
@@ -0,0 +1,25 @@
1
+ // A socket route's open socket, as its handler holds it. The sidecar holds the socket itself — its
2
+ // access rule, its limits, its close codes — and relays each frame over `Http.HandleSocket`.
3
+ /** Thrown by `Socket.send` once the socket is closed. A handler that lets it escape has ended as a
4
+ * handler ends when its client goes: it is not a failure. */
5
+ export class SocketClosed extends Error {
6
+ constructor(message = "the socket is closed") {
7
+ super(message);
8
+ this.name = "SocketClosed";
9
+ }
10
+ }
11
+ /** A socket over two functions: what the servicer and the endpoint test kit each give a handler. */
12
+ export function socketOf(receive, send) {
13
+ return Object.freeze({
14
+ receive,
15
+ send,
16
+ [Symbol.asyncIterator]() {
17
+ return {
18
+ async next() {
19
+ const text = await receive();
20
+ return text === undefined ? { value: undefined, done: true } : { value: text, done: false };
21
+ },
22
+ };
23
+ },
24
+ });
25
+ }
package/dist/spec.d.ts CHANGED
@@ -2,7 +2,7 @@ import { type MessageInitShape } from "@bufbuild/protobuf";
2
2
  import { Endpoint_Acl, type CallerMatcherSchema, type Spec } from "./_proto/ankka/protocol/v1/discovery_pb.ts";
3
3
  import type { Registry } from "./service.ts";
4
4
  import type { Acl } from "./routes.ts";
5
- export declare const PROTOCOL_VERSION = "1.2";
5
+ export declare const PROTOCOL_VERSION = "1.14";
6
6
  export declare const SDK_NAME = "ankka-typescript";
7
7
  export declare function aclToProto(acl: Acl): Endpoint_Acl;
8
8
  /** The callers a CALLERS acl names, as discovery carries them; empty for every other rule. */
package/dist/spec.js CHANGED
@@ -1,12 +1,23 @@
1
1
  // Renders the discovery Spec from the registry: what the sidecar hosts is exactly what is registered.
2
2
  import { create } from "@bufbuild/protobuf";
3
- import { Endpoint_Acl, SpecSchema } from "./_proto/ankka/protocol/v1/discovery_pb.js";
3
+ import { Endpoint_Acl, Kind, SpecSchema, StartFrom_Named } from "./_proto/ankka/protocol/v1/discovery_pb.js";
4
4
  import { resultSchemaJson } from "./autonomous.js";
5
5
  import { kindToProto } from "./kinds.js";
6
6
  import { toJsonSchema } from "./schema.js";
7
7
  import { isCodec } from "./codec.js";
8
8
  import { VERSION } from "./version.js";
9
- export const PROTOCOL_VERSION = "1.2";
9
+ import { approvalToProto } from "./approvals.js";
10
+ import { serverToProto } from "./mcp.js";
11
+ function toolInit(t) {
12
+ const approval = approvalToProto(t.approval);
13
+ return {
14
+ name: t.name,
15
+ description: t.description,
16
+ inputSchemaJson: JSON.stringify(isCodec(t.input) ? { type: "object" } : toJsonSchema(t.input)),
17
+ ...(approval !== undefined ? { approval } : {}),
18
+ };
19
+ }
20
+ export const PROTOCOL_VERSION = "1.14";
10
21
  export const SDK_NAME = "ankka-typescript";
11
22
  export function aclToProto(acl) {
12
23
  if (typeof acl === "object")
@@ -43,9 +54,21 @@ function handlerInits(handlers) {
43
54
  .sort((a, b) => a.name.localeCompare(b.name));
44
55
  }
45
56
  function sourceInit(source) {
46
- return "topic" in source
47
- ? { source: { case: "topic", value: source.topic } }
48
- : { source: { case: "component", value: { kind: kindToProto(source.component.kind), id: source.component.id } } };
57
+ if (!("topic" in source))
58
+ return { source: { case: "component", value: { kind: kindToProto(source.component.kind), id: source.component.id } } };
59
+ const start = source.startFrom;
60
+ // 1.14: the contract, the broker and parallel, absent when not declared.
61
+ const options = {
62
+ ...(source.contract !== undefined ? { contract: { name: source.contract.name, fingerprint: source.contract.fingerprint } } : {}),
63
+ ...(source.broker !== undefined ? { broker: source.broker } : {}),
64
+ ...(source.parallel === true ? { parallel: true } : {}),
65
+ };
66
+ if (start === undefined)
67
+ return { source: { case: "topic", value: source.topic }, ...options };
68
+ const position = start.kind === "at"
69
+ ? { case: "atMillis", value: BigInt(start.atMillis) }
70
+ : { case: "named", value: start.kind === "earliest" ? StartFrom_Named.EARLIEST : StartFrom_Named.LATEST };
71
+ return { source: { case: "topic", value: source.topic }, startFrom: { position }, ...options };
49
72
  }
50
73
  function recoveryInit(r) {
51
74
  return { maxRetries: r.maxRetries, ...(r.failoverTo !== undefined ? { failoverTo: r.failoverTo } : {}) };
@@ -65,7 +88,7 @@ function settingsInit(s) {
65
88
  };
66
89
  }
67
90
  function componentInit(c) {
68
- const base = { kind: kindToProto(c.kind), id: c.id };
91
+ const base = { kind: c.kind === "keyed-view" ? Kind.VIEW : kindToProto(c.kind), id: c.id };
69
92
  switch (c.kind) {
70
93
  case "autonomous-agent":
71
94
  return autonomousInit(c);
@@ -80,9 +103,58 @@ function componentInit(c) {
80
103
  detail: { case: "workflow", value: { steps: [...c.steps.keys()].sort(), ...(c.settings ? { settings: settingsInit(c.settings) } : {}) } },
81
104
  };
82
105
  case "view":
83
- return { ...base, handlers: [], detail: { case: "view", value: { source: sourceInit(c.source), rowManifest: c.rowCodec.manifest, queries: [...c.queries] } } };
106
+ return {
107
+ ...base,
108
+ handlers: [],
109
+ detail: {
110
+ case: "view",
111
+ value: {
112
+ source: sourceInit(c.source),
113
+ rowManifest: c.rowCodec.manifest,
114
+ queries: [...c.queries],
115
+ ...(c.version !== undefined ? { version: c.version } : {}),
116
+ declaredQueries: c.declared.map((q) => ({ name: q.name, statement: q.statement })),
117
+ },
118
+ },
119
+ };
120
+ case "keyed-view":
121
+ return {
122
+ ...base,
123
+ handlers: [],
124
+ detail: {
125
+ case: "view",
126
+ value: {
127
+ rowManifest: c.rowCodec.manifest,
128
+ queries: [],
129
+ ...(c.version !== undefined ? { version: c.version } : {}),
130
+ sources: [...c.sources.values()].map((s) => sourceInit({ component: { kind: s.kind, id: s.id } })),
131
+ declaredQueries: c.declared.map((q) => ({ name: q.name, statement: q.statement })),
132
+ },
133
+ },
134
+ };
84
135
  case "consumer":
85
- return { ...base, handlers: [], detail: { case: "consumer", value: { source: sourceInit(c.source), ...(c.producesTo !== undefined ? { producesTo: c.producesTo } : {}) } } };
136
+ return {
137
+ ...base,
138
+ handlers: [],
139
+ detail: {
140
+ case: "consumer",
141
+ value: {
142
+ source: sourceInit(c.source),
143
+ ...(c.producesTo !== undefined ? { producesTo: c.producesTo } : {}),
144
+ ...(c.version !== undefined ? { version: c.version } : {}),
145
+ // 1.14: the publication with its contract and broker; `producesTo` stays for an older sidecar.
146
+ ...(c.produces !== undefined
147
+ ? {
148
+ produces: {
149
+ topic: c.produces.topic,
150
+ ...(c.produces.contract !== undefined ? { contract: { name: c.produces.contract.name, fingerprint: c.produces.contract.fingerprint } } : {}),
151
+ ...(c.produces.broker !== undefined ? { broker: c.produces.broker } : {}),
152
+ },
153
+ }
154
+ : {}),
155
+ },
156
+ },
157
+ };
86
158
  case "timed-action":
87
159
  return { ...base, handlers: handlerInits(c.actions), detail: { case: "timedAction", value: {} } };
88
160
  case "agent":
@@ -94,10 +166,10 @@ function componentInit(c) {
94
166
  value: {
95
167
  role: c.role,
96
168
  maxToolCallSteps: c.maxToolCallSteps,
97
- tools: [...c.tools.values()]
98
- .sort((a, b) => a.name.localeCompare(b.name))
99
- .map((t) => ({ name: t.name, description: t.description, inputSchemaJson: JSON.stringify(isCodec(t.input) ? { type: "object" } : toJsonSchema(t.input)) })),
169
+ tools: [...c.tools.values()].sort((a, b) => a.name.localeCompare(b.name)).map(toolInit),
100
170
  guardrails: [...c.guardrails.keys()].sort(),
171
+ mcpServers: [...c.mcpServers.values()].sort((a, b) => a.name.localeCompare(b.name)).map(serverToProto),
172
+ resultGuardrails: [...c.resultGuardrails.keys()].sort(),
101
173
  },
102
174
  },
103
175
  };
@@ -123,10 +195,10 @@ function autonomousInit(c) {
123
195
  description: cls.description,
124
196
  ...(cls.instructions !== undefined ? { instructions: cls.instructions } : {}),
125
197
  ...(cls.model !== undefined ? { model: cls.model } : {}),
126
- tools: [...c.tools.values()]
127
- .sort((a, b) => a.name.localeCompare(b.name))
128
- .map((t) => ({ name: t.name, description: t.description, inputSchemaJson: JSON.stringify(isCodec(t.input) ? { type: "object" } : toJsonSchema(t.input)) })),
198
+ tools: [...c.tools.values()].sort((a, b) => a.name.localeCompare(b.name)).map(toolInit),
129
199
  guardrails: [...c.guardrails.keys()].sort(),
200
+ mcpServers: [...c.mcpServers.values()].sort((a, b) => a.name.localeCompare(b.name)).map(serverToProto),
201
+ resultGuardrails: [...c.resultGuardrails.keys()].sort(),
130
202
  taskTypes: [...c.taskTypes.values()].map((t) => {
131
203
  const schema = resultSchemaJson(t);
132
204
  return { name: t.name, description: t.description, rules: t.rules.map((r) => r.name), ...(schema !== undefined ? { resultSchemaJson: schema } : {}) };
@@ -150,6 +222,7 @@ export function renderSpec(registry) {
150
222
  template: r.template,
151
223
  hasBody: r.body !== undefined,
152
224
  streaming: r.streaming,
225
+ socket: r.socket,
153
226
  ...(r.acl !== undefined ? { acl: aclToProto(r.acl), allowCallers: callersToProto(r.acl) } : {}),
154
227
  })),
155
228
  }));
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Where a topic source begins, the first time its consumer group reads a partition. Applied once and
3
+ * committed at once, so a restart, a rebalance or a new instance resumes where the group got to,
4
+ * never from here again.
5
+ */
6
+ export type StartFrom = {
7
+ readonly kind: "earliest";
8
+ } | {
9
+ readonly kind: "latest";
10
+ } | {
11
+ readonly kind: "at";
12
+ readonly atMillis: number;
13
+ };
14
+ export declare const StartFrom: Readonly<{
15
+ /** The oldest message the broker still holds. */
16
+ earliest: StartFrom;
17
+ /** After the newest: only what is published from then on. */
18
+ latest: StartFrom;
19
+ /** The first message published at or after `when`. */
20
+ at(when: Date): StartFrom;
21
+ }>;
22
+ /** The first protocol in which a process can declare a start position or a version. */
23
+ export declare const START_POSITION_PROTOCOL: readonly [number, number];
24
+ export declare function isStartFrom(value: unknown): value is StartFrom;
25
+ /** The first protocol in which a process can declare a view's queries. */
26
+ export declare const DECLARED_QUERY_PROTOCOL: readonly [number, number];
27
+ /** Whether a sidecar speaking `protocolVersion` would ignore a start position and a version. */
28
+ export declare function olderThanStartPositions(protocolVersion: string): boolean;
29
+ /** Whether a sidecar speaking `protocolVersion` would ignore a view's declared queries. */
30
+ export declare function olderThanDeclaredQueries(protocolVersion: string): boolean;
31
+ /** Whether a sidecar speaking `protocolVersion` would ignore a topic's contract, broker and parallel reading. */
32
+ export declare function olderThanContracts(protocolVersion: string): boolean;