ankka 0.7.1 → 0.9.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.
- package/dist/_proto/ankka/protocol/v1/agent_pb.d.ts +95 -0
- package/dist/_proto/ankka/protocol/v1/agent_pb.js +16 -1
- package/dist/_proto/ankka/protocol/v1/discovery_pb.d.ts +235 -1
- package/dist/_proto/ankka/protocol/v1/discovery_pb.js +45 -5
- package/dist/_proto/ankka/protocol/v1/endpoint_pb.d.ts +63 -0
- package/dist/_proto/ankka/protocol/v1/endpoint_pb.js +15 -5
- package/dist/_proto/ankka/protocol/v1/wasm_pb.d.ts +307 -0
- package/dist/_proto/ankka/protocol/v1/wasm_pb.js +72 -0
- package/dist/autonomous.d.ts +154 -0
- package/dist/autonomous.js +199 -0
- package/dist/client.d.ts +13 -0
- package/dist/client.js +49 -0
- package/dist/context.d.ts +15 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/kinds.d.ts +1 -1
- package/dist/kinds.js +1 -0
- package/dist/routes.d.ts +41 -5
- package/dist/routes.js +24 -1
- package/dist/server/agent.d.ts +8 -2
- package/dist/server/agent.js +59 -2
- package/dist/server/http.js +13 -0
- package/dist/service.d.ts +11 -1
- package/dist/service.js +46 -5
- package/dist/spec.d.ts +6 -3
- package/dist/spec.js +62 -6
- package/dist/testkit/integration.js +5 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Autonomous agents: a durable agent that is handed a task and works it on its own, iteration by
|
|
2
|
+
// iteration, until it completes the task with a result its type's rules accept, fails it, or spends
|
|
3
|
+
// its budget. This process declares the agent — what it is for, its tools and guardrails, the task
|
|
4
|
+
// types it accepts — and the sidecar runs the loop, the model and every record. The process is asked
|
|
5
|
+
// to run a tool, check a guardrail or check a task rule, each naming the task it is for.
|
|
6
|
+
//
|
|
7
|
+
// export const ANSWER = taskType("answer", "Answer a question", { result: Answer, rules: [taskRule("cites-sources", (a) => a.sources.length ? accepted() : rejected("cite something"))] })
|
|
8
|
+
// export class Answerer extends AutonomousAgent {
|
|
9
|
+
// static readonly componentId = "answerer"
|
|
10
|
+
// static readonly description = "Answers questions"
|
|
11
|
+
// static readonly tools = { lookup: tool("lookup", "Looks a topic up", Topic, (a: Answerer, t) => a.lookup(t)) }
|
|
12
|
+
// static readonly accepts = [taskAcceptance(ANSWER, { maxIterations: 5 })]
|
|
13
|
+
// }
|
|
14
|
+
//
|
|
15
|
+
// A tool may run more than once for one request of the model — after a crash, the last recorded
|
|
16
|
+
// request's tools run again — so a tool with a side effect should tolerate a repeat.
|
|
17
|
+
import { decodeJsonValue, reviver } from "./json.js";
|
|
18
|
+
import { toJsonSchema } from "./schema.js";
|
|
19
|
+
export const COMPLETE_TASK = "complete_task";
|
|
20
|
+
export const FAIL_TASK = "fail_task";
|
|
21
|
+
export const accepted = () => Object.freeze({ verdict: "accepted" });
|
|
22
|
+
export const rejected = (reason) => Object.freeze({ verdict: "rejected", reason });
|
|
23
|
+
export function taskRule(name, check) {
|
|
24
|
+
if (typeof name !== "string" || name.trim() === "")
|
|
25
|
+
throw new TypeError("a task rule needs a name");
|
|
26
|
+
return Object.freeze({ name, check });
|
|
27
|
+
}
|
|
28
|
+
export function taskType(name, description, options = {}) {
|
|
29
|
+
if (typeof name !== "string" || name.trim() === "")
|
|
30
|
+
throw new TypeError("a task type needs a name");
|
|
31
|
+
if (typeof description !== "string" || description.trim() === "")
|
|
32
|
+
throw new TypeError(`task type ${name} needs a description`);
|
|
33
|
+
const rules = options.rules ?? [];
|
|
34
|
+
const names = rules.map((r) => r.name);
|
|
35
|
+
if (new Set(names).size !== names.length)
|
|
36
|
+
throw new TypeError(`task type ${name} declares a rule twice`);
|
|
37
|
+
return Object.freeze({ name, description, result: options.result, rules: Object.freeze([...rules]) });
|
|
38
|
+
}
|
|
39
|
+
/** Decodes a stored result as the task type's. */
|
|
40
|
+
export function decodeResult(type, stored) {
|
|
41
|
+
const json = JSON.parse(stored, reviver);
|
|
42
|
+
return type.result ? decodeJsonValue(type.result, json) : json;
|
|
43
|
+
}
|
|
44
|
+
export function taskAcceptance(type, options = {}) {
|
|
45
|
+
return Object.freeze({ taskType: type, maxIterations: options.maxIterations ?? 10 });
|
|
46
|
+
}
|
|
47
|
+
export class AutonomousAgent {
|
|
48
|
+
#sessionId = "";
|
|
49
|
+
#client;
|
|
50
|
+
/** The task a tool, a guardrail or a rule is running for. */
|
|
51
|
+
get taskId() {
|
|
52
|
+
return this.#sessionId.startsWith("task:") ? this.#sessionId.slice("task:".length) : this.#sessionId;
|
|
53
|
+
}
|
|
54
|
+
get client() {
|
|
55
|
+
if (!this.#client)
|
|
56
|
+
throw new Error("client is only available inside a tool, a guardrail or a rule");
|
|
57
|
+
return this.#client;
|
|
58
|
+
}
|
|
59
|
+
/** @internal */
|
|
60
|
+
get _kind() {
|
|
61
|
+
return "autonomous-agent";
|
|
62
|
+
}
|
|
63
|
+
/** @internal */
|
|
64
|
+
_bind(sessionId, client) {
|
|
65
|
+
this.#sessionId = sessionId;
|
|
66
|
+
this.#client = client;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** The discovery form of a result schema. */
|
|
70
|
+
export function resultSchemaJson(type) {
|
|
71
|
+
return type.result ? JSON.stringify(toJsonSchema(type.result)) : undefined;
|
|
72
|
+
}
|
|
73
|
+
const TASK = "ankka-task";
|
|
74
|
+
const INSTANCE = "ankka-agent-instance";
|
|
75
|
+
const ENDED = new Set(["completed", "failed", "cancelled"]);
|
|
76
|
+
/** Creates tasks: `await client.tasks.create(ANSWER, "How many?")` answers the new task's id. */
|
|
77
|
+
export class Tasks {
|
|
78
|
+
#client;
|
|
79
|
+
constructor(client) {
|
|
80
|
+
this.#client = client;
|
|
81
|
+
}
|
|
82
|
+
/** A dependency that does not exist is refused before anything is written; one that has already ended cancels the new task. */
|
|
83
|
+
async create(type, instructions, options = {}) {
|
|
84
|
+
const id = options.id ?? crypto.randomUUID();
|
|
85
|
+
const dependencies = [...new Set(options.dependsOn ?? [])];
|
|
86
|
+
for (const dep of dependencies)
|
|
87
|
+
await this.#client._raw("event-sourced", TASK, dep, "get");
|
|
88
|
+
await this.#client._raw("event-sourced", TASK, id, "create", {
|
|
89
|
+
typeName: type.name,
|
|
90
|
+
instructions,
|
|
91
|
+
attachments: (options.attachments ?? []).map((a) => ({
|
|
92
|
+
name: a.name,
|
|
93
|
+
contentType: a.contentType,
|
|
94
|
+
content: a.uri !== undefined ? { type: "Reference", uri: a.uri } : { type: "Inline", text: a.content ?? "" },
|
|
95
|
+
})),
|
|
96
|
+
dependencies,
|
|
97
|
+
});
|
|
98
|
+
for (const dep of dependencies) {
|
|
99
|
+
const answer = (await this.#client._raw("event-sourced", TASK, dep, "add-dependent", { taskId: id }));
|
|
100
|
+
if (answer?.alreadyEnded) {
|
|
101
|
+
await this.#client._raw("event-sourced", TASK, id, "cancel", { reason: `dependency '${dep}' ${answer.alreadyEnded}` });
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return id;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Calls about one task. */
|
|
109
|
+
export class TaskCalls {
|
|
110
|
+
#client;
|
|
111
|
+
taskId;
|
|
112
|
+
constructor(client, taskId) {
|
|
113
|
+
this.#client = client;
|
|
114
|
+
this.taskId = taskId;
|
|
115
|
+
}
|
|
116
|
+
async get(type) {
|
|
117
|
+
const record = (await this.#client._raw("event-sourced", TASK, this.taskId, "get"));
|
|
118
|
+
if (type && record.typeName !== type.name)
|
|
119
|
+
throw new TypeError(`task '${this.taskId}' is a '${record.typeName}' task, not '${type.name}'`);
|
|
120
|
+
const stored = record.result;
|
|
121
|
+
return Object.freeze({
|
|
122
|
+
id: record.id,
|
|
123
|
+
typeName: record.typeName,
|
|
124
|
+
status: record.status,
|
|
125
|
+
result: stored === undefined || stored === null ? undefined : type ? decodeResult(type, stored) : JSON.parse(stored),
|
|
126
|
+
reason: record.reason ?? undefined,
|
|
127
|
+
iterations: record.iterations ?? 0,
|
|
128
|
+
assignee: record.assignee ?? undefined,
|
|
129
|
+
record,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/** Waits until the task has completed, failed or been cancelled. */
|
|
133
|
+
async wait(type, options = {}) {
|
|
134
|
+
const deadline = Date.now() + (options.timeoutMs ?? 600_000);
|
|
135
|
+
for (;;) {
|
|
136
|
+
const snapshot = await this.get(type);
|
|
137
|
+
if (ENDED.has(snapshot.status))
|
|
138
|
+
return snapshot;
|
|
139
|
+
if (Date.now() > deadline)
|
|
140
|
+
throw new Error(`task '${this.taskId}' had not ended in time; it is ${snapshot.status}`);
|
|
141
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** Cancels the task: at once when waiting, at the end of the current iteration when being worked. */
|
|
145
|
+
async cancel(reason = "cancelled by caller") {
|
|
146
|
+
await this.#client._raw("event-sourced", TASK, this.taskId, "cancel", { reason });
|
|
147
|
+
const record = (await this.#client._raw("event-sourced", TASK, this.taskId, "get"));
|
|
148
|
+
if (record.assignee) {
|
|
149
|
+
await this.#client._raw("autonomous-agent", record.assignee.componentId, record.assignee.instanceId, "dequeue", { taskId: this.taskId, reason });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** Calls to one instance of an autonomous agent, by the id the caller chose; with no id, only `runSingleTask`. */
|
|
154
|
+
export class AutonomousAgentCalls {
|
|
155
|
+
#client;
|
|
156
|
+
componentId;
|
|
157
|
+
instanceId;
|
|
158
|
+
constructor(client, componentId, instanceId) {
|
|
159
|
+
this.#client = client;
|
|
160
|
+
this.componentId = componentId;
|
|
161
|
+
this.instanceId = instanceId;
|
|
162
|
+
}
|
|
163
|
+
#instance() {
|
|
164
|
+
if (!this.instanceId)
|
|
165
|
+
throw new TypeError("this call needs an instance id: forAutonomousAgent(agent, instanceId)");
|
|
166
|
+
return this.instanceId;
|
|
167
|
+
}
|
|
168
|
+
/** Creates the task, starts an instance on it, and answers the task's id at once. */
|
|
169
|
+
async runSingleTask(type, instructions, options = {}) {
|
|
170
|
+
const taskId = await new Tasks(this.#client).create(type, instructions, options);
|
|
171
|
+
await this.#client._raw("autonomous-agent", this.componentId, crypto.randomUUID(), "run-single-task", { taskId });
|
|
172
|
+
return taskId;
|
|
173
|
+
}
|
|
174
|
+
assign(...taskIds) {
|
|
175
|
+
return this.#client._raw("autonomous-agent", this.componentId, this.#instance(), "assign", { taskIds });
|
|
176
|
+
}
|
|
177
|
+
async suspend() {
|
|
178
|
+
await this.#client._raw("autonomous-agent", this.componentId, this.#instance(), "suspend");
|
|
179
|
+
}
|
|
180
|
+
async resume() {
|
|
181
|
+
await this.#client._raw("autonomous-agent", this.componentId, this.#instance(), "resume");
|
|
182
|
+
}
|
|
183
|
+
/** Stops the instance for good; its tasks go back to pending for another to take. */
|
|
184
|
+
async terminate() {
|
|
185
|
+
await this.#client._raw("autonomous-agent", this.componentId, this.#instance(), "terminate");
|
|
186
|
+
}
|
|
187
|
+
async state() {
|
|
188
|
+
const r = (await this.#client._raw("event-sourced", INSTANCE, `${this.componentId}/${this.#instance()}`, "get"));
|
|
189
|
+
const current = r.current;
|
|
190
|
+
const phase = r.terminated ? "terminated" : r.suspended ? "suspended" : current ? "working" : (r.queue ?? []).length > 0 ? "waiting" : "idle";
|
|
191
|
+
return Object.freeze({ phase, currentTask: current?.taskId, iteration: current?.iteration ?? 0, queued: [...(r.queue ?? [])] });
|
|
192
|
+
}
|
|
193
|
+
/** What the instance does from now on, as it happens. Nothing is replayed. */
|
|
194
|
+
async *notifications() {
|
|
195
|
+
for await (const text of this.#client._rawStream("autonomous-agent", this.componentId, this.#instance(), "notifications")) {
|
|
196
|
+
yield JSON.parse(text);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { type Metadata } from "./effects/common.ts";
|
|
|
5
5
|
import type { HandlerRef } from "./handlers.ts";
|
|
6
6
|
import { type ComponentKind } from "./kinds.ts";
|
|
7
7
|
import type { Duration } from "./time.ts";
|
|
8
|
+
import { AutonomousAgentCalls, TaskCalls, Tasks } from "./autonomous.ts";
|
|
8
9
|
/** A component class as the typed client sees it: an id and, on its prototype, its kind. */
|
|
9
10
|
export interface ComponentRef {
|
|
10
11
|
readonly componentId: string;
|
|
@@ -82,6 +83,18 @@ export declare class ComponentClient {
|
|
|
82
83
|
forKeyValueEntity(componentId: string, entityId: string): Calls;
|
|
83
84
|
forWorkflow(componentId: string, workflowId: string): Calls;
|
|
84
85
|
forAgent(componentId: string, sessionId: string): Calls;
|
|
86
|
+
/** Creates tasks for autonomous agents. */
|
|
87
|
+
get tasks(): Tasks;
|
|
88
|
+
/** Calls about one task. */
|
|
89
|
+
forTask(taskId: string): TaskCalls;
|
|
90
|
+
/** One instance of an autonomous agent, by the id the caller chose; without one, only `runSingleTask`, on an instance the platform names. */
|
|
91
|
+
forAutonomousAgent(agent: {
|
|
92
|
+
readonly componentId: string;
|
|
93
|
+
} | string, instanceId?: string): AutonomousAgentCalls;
|
|
94
|
+
/** @internal A call with a JSON body and a JSON reply, as the platform's own components take them. */
|
|
95
|
+
_raw(kind: ComponentKind, componentId: string, entityId: string, name: string, body?: unknown): Promise<unknown>;
|
|
96
|
+
/** @internal A streaming call whose tokens are text. */
|
|
97
|
+
_rawStream(kind: ComponentKind, componentId: string, entityId: string, name: string): AsyncIterable<string>;
|
|
85
98
|
/** Typed calls through a component class's handler table: `client.of(ShoppingCartEntity, cartId).call(ShoppingCartEntity.handlers.addItem).invoke(item)`. */
|
|
86
99
|
of<C>(component: ComponentRef & {
|
|
87
100
|
new (): C;
|
package/dist/client.js
CHANGED
|
@@ -12,6 +12,7 @@ import { decodeJsonValue, reviver } from "./json.js";
|
|
|
12
12
|
import { errorCodeFromProto, kindToProto } from "./kinds.js";
|
|
13
13
|
import { metadataToProto } from "./context.js";
|
|
14
14
|
import { encodePayload, decodePayload, EMPTY_PAYLOAD } from "./server/payloads.js";
|
|
15
|
+
import { AutonomousAgentCalls, TaskCalls, Tasks } from "./autonomous.js";
|
|
15
16
|
function stubOf(connection) {
|
|
16
17
|
if (!connection.stub) {
|
|
17
18
|
connection.transport = createGrpcTransport({ baseUrl: `http://${connection.address}` });
|
|
@@ -219,6 +220,54 @@ export class ComponentClient {
|
|
|
219
220
|
forAgent(componentId, sessionId) {
|
|
220
221
|
return new Calls(this.#connection, this.#metadata, "agent", componentId, sessionId);
|
|
221
222
|
}
|
|
223
|
+
/** Creates tasks for autonomous agents. */
|
|
224
|
+
get tasks() {
|
|
225
|
+
return new Tasks(this);
|
|
226
|
+
}
|
|
227
|
+
/** Calls about one task. */
|
|
228
|
+
forTask(taskId) {
|
|
229
|
+
return new TaskCalls(this, taskId);
|
|
230
|
+
}
|
|
231
|
+
/** One instance of an autonomous agent, by the id the caller chose; without one, only `runSingleTask`, on an instance the platform names. */
|
|
232
|
+
forAutonomousAgent(agent, instanceId) {
|
|
233
|
+
return new AutonomousAgentCalls(this, typeof agent === "string" ? agent : agent.componentId, instanceId);
|
|
234
|
+
}
|
|
235
|
+
/** @internal A call with a JSON body and a JSON reply, as the platform's own components take them. */
|
|
236
|
+
async _raw(kind, componentId, entityId, name, body) {
|
|
237
|
+
const answer = await stubOf(this.#connection).invoke({
|
|
238
|
+
kind: kindToProto(kind),
|
|
239
|
+
componentId,
|
|
240
|
+
entityId,
|
|
241
|
+
name,
|
|
242
|
+
payload: { contentType: "application/json", manifest: "", data: body === undefined ? new Uint8Array() : new TextEncoder().encode(JSON.stringify(body)) },
|
|
243
|
+
metadata: metadataToProto(this.#metadata),
|
|
244
|
+
});
|
|
245
|
+
switch (answer.result.case) {
|
|
246
|
+
case "reply": {
|
|
247
|
+
const data = answer.result.value.payload?.data;
|
|
248
|
+
return data && data.length > 0 ? JSON.parse(new TextDecoder().decode(data), reviver) : undefined;
|
|
249
|
+
}
|
|
250
|
+
case "error":
|
|
251
|
+
throw new CommandError(errorOf(answer.result.value));
|
|
252
|
+
default:
|
|
253
|
+
throw new CommandError({ message: "the sidecar answered nothing", code: "INTERNAL" });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/** @internal A streaming call whose tokens are text. */
|
|
257
|
+
async *_rawStream(kind, componentId, entityId, name) {
|
|
258
|
+
const request = { kind: kindToProto(kind), componentId, entityId, name, payload: EMPTY_PAYLOAD, metadata: metadataToProto(this.#metadata) };
|
|
259
|
+
for await (const token of stubOf(this.#connection).invokeStream(request)) {
|
|
260
|
+
switch (token.token.case) {
|
|
261
|
+
case "text":
|
|
262
|
+
yield token.token.value;
|
|
263
|
+
break;
|
|
264
|
+
case "completed":
|
|
265
|
+
return;
|
|
266
|
+
case "failed":
|
|
267
|
+
throw new CommandError(errorOf(token.token.value));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
222
271
|
/** Typed calls through a component class's handler table: `client.of(ShoppingCartEntity, cartId).call(ShoppingCartEntity.handlers.addItem).invoke(item)`. */
|
|
223
272
|
of(component, entityId) {
|
|
224
273
|
return new TypedCalls(new Calls(this.#connection, this.#metadata, component.prototype._kind, component.componentId, entityId));
|
package/dist/context.d.ts
CHANGED
|
@@ -27,6 +27,19 @@ export interface Principal {
|
|
|
27
27
|
readonly emailVerified: boolean;
|
|
28
28
|
readonly roles: readonly string[];
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Which workload sent a request, as the platform established it from the client certificate: the gateway
|
|
32
|
+
* (the internet), a named service, or — outside a cluster, where there is no certificate — the local machine.
|
|
33
|
+
*/
|
|
34
|
+
export type Caller = {
|
|
35
|
+
readonly kind: "gateway";
|
|
36
|
+
} | {
|
|
37
|
+
readonly kind: "service";
|
|
38
|
+
readonly project: string;
|
|
39
|
+
readonly name: string;
|
|
40
|
+
} | {
|
|
41
|
+
readonly kind: "local";
|
|
42
|
+
};
|
|
30
43
|
/** Query parameters in request order; a name may repeat. */
|
|
31
44
|
export declare class Query {
|
|
32
45
|
private readonly pairs;
|
|
@@ -52,6 +65,8 @@ export interface RequestContext<P = Readonly<Record<string, unknown>>> {
|
|
|
52
65
|
/** The caller, when the route's access rule is `authenticated`; `null` otherwise. */
|
|
53
66
|
readonly principal: Principal | null;
|
|
54
67
|
readonly metadata: Metadata;
|
|
68
|
+
/** Which workload sent the request. Always present; see `Caller`. */
|
|
69
|
+
readonly caller: Caller;
|
|
55
70
|
}
|
|
56
71
|
/** Runs `fn` with `request` as the current request, for the asynchronous work it starts. */
|
|
57
72
|
export declare function withRequest<T>(request: RequestContext, fn: () => T): T;
|
package/dist/index.d.ts
CHANGED
|
@@ -17,9 +17,10 @@ export { Consumer, type ConsumerClass } from "./consumer.ts";
|
|
|
17
17
|
export { TimedAction, type TimedActionClass } from "./timedAction.ts";
|
|
18
18
|
export { AgentEffects, type AgentEffect } from "./effects/agent.ts";
|
|
19
19
|
export { Agent, type AgentClass } from "./agent.ts";
|
|
20
|
+
export { AutonomousAgent, AutonomousAgentCalls, TaskCalls, Tasks, accepted, rejected, taskAcceptance, taskRule, taskType, type Accepted, type AgentState, type Attachment, type AutonomousAgentClass, type AutonomousSettings, type Notification, type Rejected, type TaskAcceptance, type TaskRule, type TaskSnapshot, type TaskType, } from "./autonomous.ts";
|
|
20
21
|
export { Endpoint, type EndpointClass } from "./endpoint.ts";
|
|
21
|
-
export { Acl, HttpProblem, get, post, put, patch, del, sse, type RouteRef, type RouteOptions, type RouteTable, type Params, type ParamNames, type HttpMethod } from "./routes.ts";
|
|
22
|
-
export { type CommandContext, type RequestContext, type Principal, Query, Headers } from "./context.ts";
|
|
22
|
+
export { Acl, Callers, type CallerMatcher, type CallersAcl, HttpProblem, get, post, put, patch, del, sse, type RouteRef, type RouteOptions, type RouteTable, type Params, type ParamNames, type HttpMethod } from "./routes.ts";
|
|
23
|
+
export { type CommandContext, type RequestContext, type Principal, type Caller, Query, Headers } from "./context.ts";
|
|
23
24
|
export { ComponentClient, Calls, TypedCalls, Invocation, Views, Timers, type TimerTarget, type ComponentRef } from "./client.ts";
|
|
24
25
|
export { Ankka, ServiceBuilder, Registry, RegistrationError, type ServiceOptions, type RegisteredComponent, type RegisteredEndpoint, type Source, type RegisteredEventSourced, type RegisteredKeyValue, type RegisteredWorkflow, type RegisteredView, type RegisteredConsumer, type RegisteredTimedAction, type RegisteredAgent, } from "./service.ts";
|
|
25
26
|
export { Server, type ServerOptions } from "./server/server.ts";
|
package/dist/index.js
CHANGED
|
@@ -28,8 +28,9 @@ export { Consumer } from "./consumer.js";
|
|
|
28
28
|
export { TimedAction } from "./timedAction.js";
|
|
29
29
|
export { AgentEffects } from "./effects/agent.js";
|
|
30
30
|
export { Agent } from "./agent.js";
|
|
31
|
+
export { AutonomousAgent, AutonomousAgentCalls, TaskCalls, Tasks, accepted, rejected, taskAcceptance, taskRule, taskType, } from "./autonomous.js";
|
|
31
32
|
export { Endpoint } from "./endpoint.js";
|
|
32
|
-
export { Acl, HttpProblem, get, post, put, patch, del, sse } from "./routes.js";
|
|
33
|
+
export { Acl, Callers, HttpProblem, get, post, put, patch, del, sse } from "./routes.js";
|
|
33
34
|
export { Query, Headers } from "./context.js";
|
|
34
35
|
export { ComponentClient, Calls, TypedCalls, Invocation, Views, Timers } from "./client.js";
|
|
35
36
|
export { Ankka, ServiceBuilder, Registry, RegistrationError, } from "./service.js";
|
package/dist/kinds.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Kind as ProtoKind } from "./_proto/ankka/protocol/v1/discovery_pb.ts";
|
|
2
2
|
import { ErrorCode as ProtoErrorCode } from "./_proto/ankka/protocol/v1/payload_pb.ts";
|
|
3
3
|
import { ErrorCode } from "./effects/common.ts";
|
|
4
|
-
export type ComponentKind = "event-sourced" | "key-value" | "workflow" | "view" | "consumer" | "timed-action" | "agent";
|
|
4
|
+
export type ComponentKind = "event-sourced" | "key-value" | "workflow" | "view" | "consumer" | "timed-action" | "agent" | "autonomous-agent";
|
|
5
5
|
export declare function kindToProto(kind: ComponentKind): ProtoKind;
|
|
6
6
|
export declare function kindFromProto(kind: ProtoKind): ComponentKind;
|
|
7
7
|
export declare function errorCodeToProto(code: ErrorCode): ProtoErrorCode;
|
package/dist/kinds.js
CHANGED
|
@@ -10,6 +10,7 @@ const KIND_TO_PROTO = Object.freeze({
|
|
|
10
10
|
consumer: ProtoKind.CONSUMER,
|
|
11
11
|
"timed-action": ProtoKind.TIMED_ACTION,
|
|
12
12
|
agent: ProtoKind.AGENT,
|
|
13
|
+
"autonomous-agent": ProtoKind.AUTONOMOUS_AGENT,
|
|
13
14
|
});
|
|
14
15
|
export function kindToProto(kind) {
|
|
15
16
|
return KIND_TO_PROTO[kind];
|
package/dist/routes.d.ts
CHANGED
|
@@ -1,13 +1,49 @@
|
|
|
1
1
|
import type { Shape } from "./codec.ts";
|
|
2
2
|
import type { RequestContext } from "./context.ts";
|
|
3
3
|
import type { Infer, Schema } from "./schema.ts";
|
|
4
|
-
/**
|
|
4
|
+
/** One kind of caller an `Acl.allowCallers` admits; build one through `Callers`. */
|
|
5
|
+
export type CallerMatcher = {
|
|
6
|
+
readonly kind: "internet";
|
|
7
|
+
} | {
|
|
8
|
+
readonly kind: "service";
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly project?: string;
|
|
11
|
+
} | {
|
|
12
|
+
readonly kind: "anyInProject";
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: "self";
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* The callers an endpoint can name: `Acl.allowCallers(Callers.internet, Callers.service("orders"))`. A named
|
|
18
|
+
* service is in this service's own project unless `project` names another.
|
|
19
|
+
*/
|
|
20
|
+
export declare const Callers: Readonly<{
|
|
21
|
+
internet: CallerMatcher;
|
|
22
|
+
anyInProject: CallerMatcher;
|
|
23
|
+
self: CallerMatcher;
|
|
24
|
+
service(name: string, options?: {
|
|
25
|
+
readonly project?: string;
|
|
26
|
+
}): CallerMatcher;
|
|
27
|
+
}>;
|
|
28
|
+
/** Only the callers named. In a cluster the caller is read from the certificate the platform issued. */
|
|
29
|
+
export interface CallersAcl {
|
|
30
|
+
readonly kind: "callers";
|
|
31
|
+
readonly callers: readonly CallerMatcher[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Who may call: everyone, no one, authenticated callers only, or only the callers named. There is no default.
|
|
35
|
+
*
|
|
36
|
+
* Outside a cluster every caller is `{ kind: "local" }`, and `allowCallers` admits it.
|
|
37
|
+
*/
|
|
38
|
+
export type Acl = "allow-all" | "deny-all" | "authenticated" | CallersAcl;
|
|
5
39
|
export declare const Acl: Readonly<{
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
40
|
+
allowAll: Acl;
|
|
41
|
+
denyAll: Acl;
|
|
42
|
+
authenticated: Acl;
|
|
43
|
+
allowCallers(first: CallerMatcher, ...rest: CallerMatcher[]): Acl;
|
|
9
44
|
}>;
|
|
10
|
-
|
|
45
|
+
/** Whether `x` is an access rule: one of the three words, or a non-empty callers rule. */
|
|
46
|
+
export declare function isAcl(x: unknown): x is Acl;
|
|
11
47
|
/** Thrown from a route handler to answer a status other than 200 with a plain-text message. */
|
|
12
48
|
export declare class HttpProblem extends Error {
|
|
13
49
|
readonly status: number;
|
package/dist/routes.js
CHANGED
|
@@ -9,12 +9,35 @@
|
|
|
9
9
|
//
|
|
10
10
|
// `req.params` is typed from the template: "/{cartId}/items" gives { cartId: string }.
|
|
11
11
|
import { isCodec } from "./codec.js";
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* The callers an endpoint can name: `Acl.allowCallers(Callers.internet, Callers.service("orders"))`. A named
|
|
14
|
+
* service is in this service's own project unless `project` names another.
|
|
15
|
+
*/
|
|
16
|
+
export const Callers = Object.freeze({
|
|
17
|
+
internet: Object.freeze({ kind: "internet" }),
|
|
18
|
+
anyInProject: Object.freeze({ kind: "anyInProject" }),
|
|
19
|
+
self: Object.freeze({ kind: "self" }),
|
|
20
|
+
service(name, options) {
|
|
21
|
+
return Object.freeze(options?.project !== undefined ? { kind: "service", name, project: options.project } : { kind: "service", name });
|
|
22
|
+
},
|
|
23
|
+
});
|
|
13
24
|
export const Acl = Object.freeze({
|
|
14
25
|
allowAll: "allow-all",
|
|
15
26
|
denyAll: "deny-all",
|
|
16
27
|
authenticated: "authenticated",
|
|
28
|
+
allowCallers(first, ...rest) {
|
|
29
|
+
return Object.freeze({ kind: "callers", callers: Object.freeze([first, ...rest]) });
|
|
30
|
+
},
|
|
17
31
|
});
|
|
32
|
+
/** Whether `x` is an access rule: one of the three words, or a non-empty callers rule. */
|
|
33
|
+
export function isAcl(x) {
|
|
34
|
+
if (x === "allow-all" || x === "deny-all" || x === "authenticated")
|
|
35
|
+
return true;
|
|
36
|
+
if (typeof x !== "object" || x === null)
|
|
37
|
+
return false;
|
|
38
|
+
const rule = x;
|
|
39
|
+
return rule.kind === "callers" && Array.isArray(rule.callers) && rule.callers.length > 0;
|
|
40
|
+
}
|
|
18
41
|
/** Thrown from a route handler to answer a status other than 200 with a plain-text message. */
|
|
19
42
|
export class HttpProblem extends Error {
|
|
20
43
|
status;
|
package/dist/server/agent.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { type GuardrailRequest, type GuardrailResult, type PlanReply, type PlanRequest, type ToolRequest, type ToolResult } from "../_proto/ankka/protocol/v1/agent_pb.ts";
|
|
1
|
+
import { type ConnectRouter } from "@connectrpc/connect";
|
|
2
|
+
import { type GuardrailRequest, type GuardrailResult, type PlanReply, type PlanRequest, type TaskResultRequest, type TaskResultVerdict, type ToolRequest, type ToolResult } from "../_proto/ankka/protocol/v1/agent_pb.ts";
|
|
3
3
|
import type { AgentEffect } from "../effects/agent.ts";
|
|
4
4
|
import type { Agent } from "../agent.ts";
|
|
5
5
|
import type { RegisteredAgent } from "../service.ts";
|
|
@@ -11,4 +11,10 @@ export declare function handlePlan(req: PlanRequest, ctx: ServerContext): Promis
|
|
|
11
11
|
export declare function runTool(registered: RegisteredAgent, agent: Agent, name: string, argumentsJson: string): Promise<string>;
|
|
12
12
|
export declare function handleTool(req: ToolRequest, ctx: ServerContext): Promise<ToolResult>;
|
|
13
13
|
export declare function handleGuardrail(req: GuardrailRequest, ctx: ServerContext): Promise<GuardrailResult>;
|
|
14
|
+
/**
|
|
15
|
+
* An autonomous agent's result: decoded as its task type's, then put through its rules in order. A
|
|
16
|
+
* rule that throws is answered as an error, which the sidecar takes as a failed iteration and tries
|
|
17
|
+
* again — never as a verdict, since nothing was decided.
|
|
18
|
+
*/
|
|
19
|
+
export declare function handleTaskResult(req: TaskResultRequest, ctx: ServerContext): Promise<TaskResultVerdict>;
|
|
14
20
|
export declare function agentRoutes(router: ConnectRouter, ctx: ServerContext): void;
|
package/dist/server/agent.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
// The agent conversation: the sidecar asks this process to plan (an AgentEffect as data), to run a tool
|
|
2
2
|
// with the model's arguments, and to check a guardrail. The loop, the memory and the model's key are
|
|
3
3
|
// the sidecar's.
|
|
4
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
4
5
|
import { create } from "@bufbuild/protobuf";
|
|
5
|
-
import { Agent as AgentService, AgentPlan_Memory, GuardrailRequest_Stage, GuardrailResultSchema, PlanReplySchema, ToolResultSchema, } from "../_proto/ankka/protocol/v1/agent_pb.js";
|
|
6
|
+
import { Agent as AgentService, AgentPlan_Memory, GuardrailRequest_Stage, GuardrailResultSchema, PlanReplySchema, TaskResultVerdictSchema, ToolResultSchema, } from "../_proto/ankka/protocol/v1/agent_pb.js";
|
|
6
7
|
import { codecFor, isCodec } from "../codec.js";
|
|
7
8
|
import { metadataFromProto } from "../context.js";
|
|
8
9
|
import { ErrorCode } from "../effects/common.js";
|
|
9
10
|
import { decodeJsonValue, reviver } from "../json.js";
|
|
10
11
|
import { errorCodeToProto } from "../kinds.js";
|
|
12
|
+
import { decodeResult } from "../autonomous.js";
|
|
11
13
|
import { decodePayload } from "./payloads.js";
|
|
12
14
|
function messageOf(e) {
|
|
13
15
|
return e instanceof Error ? e.message : String(e);
|
|
@@ -75,6 +77,9 @@ export async function runTool(registered, agent, name, argumentsJson) {
|
|
|
75
77
|
return typeof result === "string" ? result : JSON.stringify(result, (_k, v) => (typeof v === "bigint" ? v.toString() : v));
|
|
76
78
|
}
|
|
77
79
|
export async function handleTool(req, ctx) {
|
|
80
|
+
const autonomous = ctx.registry.of("autonomous-agent", req.componentId);
|
|
81
|
+
if (autonomous)
|
|
82
|
+
return handleAutonomousTool(autonomous, req, ctx);
|
|
78
83
|
const registered = ctx.registry.of("agent", req.componentId);
|
|
79
84
|
if (!registered)
|
|
80
85
|
return create(ToolResultSchema, { result: { case: "error", value: `no agent ${JSON.stringify(req.componentId)} is registered` } });
|
|
@@ -89,7 +94,7 @@ export async function handleTool(req, ctx) {
|
|
|
89
94
|
}
|
|
90
95
|
}
|
|
91
96
|
export async function handleGuardrail(req, ctx) {
|
|
92
|
-
const registered = ctx.registry.of("agent", req.componentId);
|
|
97
|
+
const registered = ctx.registry.of("agent", req.componentId) ?? ctx.registry.of("autonomous-agent", req.componentId);
|
|
93
98
|
const guardrail = registered?.guardrails.get(req.guardrail);
|
|
94
99
|
if (!registered || !guardrail)
|
|
95
100
|
return create(GuardrailResultSchema, { result: { case: "block", value: `no guardrail ${JSON.stringify(req.guardrail)} on ${req.componentId}` } });
|
|
@@ -104,10 +109,62 @@ export async function handleGuardrail(req, ctx) {
|
|
|
104
109
|
return create(GuardrailResultSchema, { result: { case: "block", value: `guardrail ${req.guardrail} failed: ${messageOf(e)}` } });
|
|
105
110
|
}
|
|
106
111
|
}
|
|
112
|
+
async function handleAutonomousTool(registered, req, ctx) {
|
|
113
|
+
const tool = registered.tools.get(req.tool);
|
|
114
|
+
if (!tool)
|
|
115
|
+
return create(ToolResultSchema, { result: { case: "error", value: `no tool ${JSON.stringify(req.tool)} on ${registered.id}` } });
|
|
116
|
+
const agent = new registered.cls();
|
|
117
|
+
agent._bind(req.sessionId, ctx.client);
|
|
118
|
+
try {
|
|
119
|
+
let input;
|
|
120
|
+
if (isCodec(tool.input))
|
|
121
|
+
input = tool.input.decode(new TextEncoder().encode(req.argumentsJson));
|
|
122
|
+
else
|
|
123
|
+
input = decodeJsonValue(tool.input, JSON.parse(req.argumentsJson || "{}", reviver));
|
|
124
|
+
const result = await tool.run(agent, input);
|
|
125
|
+
const text = typeof result === "string" ? result : JSON.stringify(result, (_k, v) => (typeof v === "bigint" ? v.toString() : v));
|
|
126
|
+
return create(ToolResultSchema, { result: { case: "ok", value: text } });
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
return create(ToolResultSchema, { result: { case: "error", value: messageOf(e) } });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* An autonomous agent's result: decoded as its task type's, then put through its rules in order. A
|
|
134
|
+
* rule that throws is answered as an error, which the sidecar takes as a failed iteration and tries
|
|
135
|
+
* again — never as a verdict, since nothing was decided.
|
|
136
|
+
*/
|
|
137
|
+
export async function handleTaskResult(req, ctx) {
|
|
138
|
+
const registered = ctx.registry.of("autonomous-agent", req.componentId);
|
|
139
|
+
const type = registered?.taskTypes.get(req.taskType);
|
|
140
|
+
if (!registered || !type)
|
|
141
|
+
throw new ConnectError(`no task type ${JSON.stringify(req.taskType)} on ${req.componentId}`, Code.NotFound);
|
|
142
|
+
let result;
|
|
143
|
+
try {
|
|
144
|
+
result = decodeResult(type, req.resultJson);
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
return create(TaskResultVerdictSchema, { verdict: { case: "malformed", value: messageOf(e) } });
|
|
148
|
+
}
|
|
149
|
+
for (const rule of type.rules) {
|
|
150
|
+
let verdict;
|
|
151
|
+
try {
|
|
152
|
+
verdict = await rule.check(result);
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
ctx.log(`ankka: rule ${registered.id}/${req.taskType}/${rule.name} threw: ${messageOf(e)}`);
|
|
156
|
+
throw new ConnectError(`rule ${rule.name} threw: ${messageOf(e)}`, Code.Internal);
|
|
157
|
+
}
|
|
158
|
+
if (verdict.verdict === "rejected")
|
|
159
|
+
return create(TaskResultVerdictSchema, { verdict: { case: "reject", value: { rule: rule.name, reason: verdict.reason } } });
|
|
160
|
+
}
|
|
161
|
+
return create(TaskResultVerdictSchema, { verdict: { case: "accept", value: {} } });
|
|
162
|
+
}
|
|
107
163
|
export function agentRoutes(router, ctx) {
|
|
108
164
|
router.service(AgentService, {
|
|
109
165
|
plan: (req) => handlePlan(req, ctx),
|
|
110
166
|
invokeTool: (req) => handleTool(req, ctx),
|
|
111
167
|
checkGuardrail: (req) => handleGuardrail(req, ctx),
|
|
168
|
+
checkTaskResult: (req) => handleTaskResult(req, ctx),
|
|
112
169
|
});
|
|
113
170
|
}
|
package/dist/server/http.js
CHANGED
|
@@ -30,6 +30,18 @@ function principalOf(req) {
|
|
|
30
30
|
return null;
|
|
31
31
|
return Object.freeze({ subject: p.subject, name: p.name ?? null, email: p.email ?? null, emailVerified: p.emailVerified, roles: Object.freeze([...p.roles]) });
|
|
32
32
|
}
|
|
33
|
+
/** The caller the sidecar established; a sidecar that predates protocol 1.1 sends none, which reads as local. */
|
|
34
|
+
function callerOf(req) {
|
|
35
|
+
const kind = req.caller?.kind;
|
|
36
|
+
switch (kind?.case) {
|
|
37
|
+
case "gateway":
|
|
38
|
+
return Object.freeze({ kind: "gateway" });
|
|
39
|
+
case "service":
|
|
40
|
+
return Object.freeze({ kind: "service", project: kind.value.project, name: kind.value.name });
|
|
41
|
+
default:
|
|
42
|
+
return Object.freeze({ kind: "local" });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
33
45
|
/** A path parameter as its declared schema: strings as they are, scalars through their text codec. */
|
|
34
46
|
function parseParam(name, text, shape) {
|
|
35
47
|
if (!shape)
|
|
@@ -82,6 +94,7 @@ export function createHttpDispatcher(ctx) {
|
|
|
82
94
|
headers: new Headers(req.headers.map((p) => [p.name, p.value])),
|
|
83
95
|
principal: principalOf(req),
|
|
84
96
|
metadata: metadataFromProto(req.metadata),
|
|
97
|
+
caller: callerOf(req),
|
|
85
98
|
});
|
|
86
99
|
const body = route.body ? codecFor(route.body).decode(req.body) : undefined;
|
|
87
100
|
return { ok: true, prepared: { endpoint, route, instance, request, body } };
|
package/dist/service.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { View, type ViewClass } from "./view.ts";
|
|
|
6
6
|
import { Consumer, type ConsumerClass } from "./consumer.ts";
|
|
7
7
|
import { TimedAction, type TimedActionClass } from "./timedAction.ts";
|
|
8
8
|
import { Agent, type AgentClass } from "./agent.ts";
|
|
9
|
+
import { AutonomousAgent, type AutonomousAgentClass, type TaskType } from "./autonomous.ts";
|
|
9
10
|
import { Endpoint, type EndpointClass } from "./endpoint.ts";
|
|
10
11
|
import type { GuardrailRef, HandlerRef, ToolRef } from "./handlers.ts";
|
|
11
12
|
import type { RouteRef, Acl } from "./routes.ts";
|
|
@@ -82,7 +83,15 @@ export interface RegisteredAgent {
|
|
|
82
83
|
readonly role: string;
|
|
83
84
|
readonly maxToolCallSteps: number;
|
|
84
85
|
}
|
|
85
|
-
export
|
|
86
|
+
export interface RegisteredAutonomousAgent {
|
|
87
|
+
readonly kind: "autonomous-agent";
|
|
88
|
+
readonly id: string;
|
|
89
|
+
readonly cls: AutonomousAgentClass<any>;
|
|
90
|
+
readonly tools: ReadonlyMap<string, ToolRef<any, any>>;
|
|
91
|
+
readonly guardrails: ReadonlyMap<string, GuardrailRef>;
|
|
92
|
+
readonly taskTypes: ReadonlyMap<string, TaskType<any>>;
|
|
93
|
+
}
|
|
94
|
+
export type RegisteredComponent = RegisteredEventSourced | RegisteredKeyValue | RegisteredWorkflow | RegisteredView | RegisteredConsumer | RegisteredTimedAction | RegisteredAgent | RegisteredAutonomousAgent;
|
|
86
95
|
export interface RegisteredEndpoint {
|
|
87
96
|
readonly id: string;
|
|
88
97
|
readonly cls: EndpointClass<any>;
|
|
@@ -127,6 +136,7 @@ export declare class ServiceBuilder {
|
|
|
127
136
|
register<M, Out, C extends Consumer<M, Out>>(cls: ConsumerClass<M, Out, C>): this;
|
|
128
137
|
register<C extends TimedAction>(cls: TimedActionClass<C>): this;
|
|
129
138
|
register<C extends Agent>(cls: AgentClass<C>): this;
|
|
139
|
+
register<C extends AutonomousAgent>(cls: AutonomousAgentClass<C>): this;
|
|
130
140
|
register<C extends Endpoint>(cls: EndpointClass<C>): this;
|
|
131
141
|
/** Validates every registration and returns the registry, or throws a `RegistrationError` naming every problem. */
|
|
132
142
|
validate(): Registry;
|