@adecore/agent-contracts 0.0.1 → 0.17.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +110 -0
- package/README.md +20 -0
- package/dist/agent.d.ts +133 -0
- package/dist/agent.js +79 -0
- package/dist/chat.d.ts +5390 -0
- package/dist/chat.js +741 -0
- package/dist/envelope.d.ts +72 -0
- package/dist/envelope.js +40 -0
- package/dist/ids.d.ts +3 -0
- package/dist/ids.js +3 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +11 -0
- package/dist/model.d.ts +282 -0
- package/dist/model.js +123 -0
- package/dist/port.d.ts +5 -0
- package/dist/port.js +1 -0
- package/dist/protocol.d.ts +4577 -0
- package/dist/protocol.js +64 -0
- package/dist/provider-accounts.d.ts +199 -0
- package/dist/provider-accounts.js +91 -0
- package/dist/task.d.ts +110 -0
- package/dist/task.js +33 -0
- package/dist/text.d.ts +1 -0
- package/dist/text.js +19 -0
- package/dist/usage.d.ts +392 -0
- package/dist/usage.js +165 -0
- package/dist/worktree.d.ts +30 -0
- package/dist/worktree.js +30 -0
- package/package.json +96 -3
- package/src/agent.ts +103 -0
- package/src/chat.ts +924 -0
- package/src/envelope.ts +59 -0
- package/src/ids.ts +5 -0
- package/src/index.ts +12 -0
- package/src/model.ts +147 -0
- package/src/port.ts +11 -0
- package/src/protocol.ts +120 -0
- package/src/provider-accounts.ts +114 -0
- package/src/task.ts +41 -0
- package/src/text.ts +20 -0
- package/src/usage.ts +204 -0
- package/src/worktree.ts +34 -0
package/src/envelope.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const ErrorSchema = z.object({
|
|
4
|
+
code: z.string().min(1),
|
|
5
|
+
message: z.string()
|
|
6
|
+
});
|
|
7
|
+
export type WireError = z.infer<typeof ErrorSchema>;
|
|
8
|
+
|
|
9
|
+
export const RequestSchema = z.object({
|
|
10
|
+
id: z.string().min(1),
|
|
11
|
+
type: z.string().min(1),
|
|
12
|
+
payload: z.unknown()
|
|
13
|
+
});
|
|
14
|
+
export type Request = z.infer<typeof RequestSchema>;
|
|
15
|
+
|
|
16
|
+
export const ReplyOkSchema = z.object({
|
|
17
|
+
id: z.string().min(1),
|
|
18
|
+
ok: z.literal(true),
|
|
19
|
+
result: z.unknown()
|
|
20
|
+
});
|
|
21
|
+
export type ReplyOk = z.infer<typeof ReplyOkSchema>;
|
|
22
|
+
|
|
23
|
+
// The id is nullable so the server can still answer a frame it could not parse at all.
|
|
24
|
+
export const ReplyErrorSchema = z.object({
|
|
25
|
+
id: z.string().min(1).nullable(),
|
|
26
|
+
ok: z.literal(false),
|
|
27
|
+
error: ErrorSchema
|
|
28
|
+
});
|
|
29
|
+
export type ReplyError = z.infer<typeof ReplyErrorSchema>;
|
|
30
|
+
|
|
31
|
+
export const ReplySchema = z.discriminatedUnion('ok', [ReplyOkSchema, ReplyErrorSchema]);
|
|
32
|
+
export type Reply = z.infer<typeof ReplySchema>;
|
|
33
|
+
|
|
34
|
+
export const EventSchema = z.object({
|
|
35
|
+
type: z.literal('event'),
|
|
36
|
+
event: z.string().min(1),
|
|
37
|
+
payload: z.unknown()
|
|
38
|
+
});
|
|
39
|
+
export type Event = z.infer<typeof EventSchema>;
|
|
40
|
+
|
|
41
|
+
export const ServerFrameSchema = z.union([ReplySchema, EventSchema]);
|
|
42
|
+
export type ServerFrame = z.infer<typeof ServerFrameSchema>;
|
|
43
|
+
|
|
44
|
+
export type ParseResult<T> = { ok: true; value: T } | { ok: false; message: string };
|
|
45
|
+
|
|
46
|
+
function toResult<T>(parsed: z.ZodSafeParseResult<T>): ParseResult<T> {
|
|
47
|
+
if (parsed.success) {
|
|
48
|
+
return { ok: true, value: parsed.data };
|
|
49
|
+
}
|
|
50
|
+
return { ok: false, message: z.prettifyError(parsed.error) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function parseRequest(input: unknown): ParseResult<Request> {
|
|
54
|
+
return toResult(RequestSchema.safeParse(input));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function parseServerFrame(input: unknown): ParseResult<ServerFrame> {
|
|
58
|
+
return toResult(ServerFrameSchema.safeParse(input));
|
|
59
|
+
}
|
package/src/ids.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './agent.ts';
|
|
2
|
+
export * from './chat.ts';
|
|
3
|
+
export * from './envelope.ts';
|
|
4
|
+
export * from './ids.ts';
|
|
5
|
+
export * from './model.ts';
|
|
6
|
+
export * from './protocol.ts';
|
|
7
|
+
export * from './provider-accounts.ts';
|
|
8
|
+
export * from './task.ts';
|
|
9
|
+
export * from './text.ts';
|
|
10
|
+
export * from './usage.ts';
|
|
11
|
+
export * from './worktree.ts';
|
|
12
|
+
export type * from './port.ts';
|
package/src/model.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AgentKindSchema } from './agent.ts';
|
|
3
|
+
|
|
4
|
+
// Options a model exposes (effort, context window, thinking). Generic descriptors instead of
|
|
5
|
+
// per-provider enums: a new model is a catalog entry, not a code change.
|
|
6
|
+
export const ModelOptionChoiceSchema = z.object({
|
|
7
|
+
id: z.string().min(1),
|
|
8
|
+
label: z.string(),
|
|
9
|
+
description: z.string().optional()
|
|
10
|
+
});
|
|
11
|
+
export type ModelOptionChoice = z.infer<typeof ModelOptionChoiceSchema>;
|
|
12
|
+
|
|
13
|
+
export const ModelOptionDescriptorSchema = z.discriminatedUnion('type', [
|
|
14
|
+
z.object({
|
|
15
|
+
id: z.string().min(1),
|
|
16
|
+
label: z.string(),
|
|
17
|
+
type: z.literal('select'),
|
|
18
|
+
choices: z.array(ModelOptionChoiceSchema).min(1),
|
|
19
|
+
defaultChoice: z.string()
|
|
20
|
+
}),
|
|
21
|
+
z.object({
|
|
22
|
+
id: z.string().min(1),
|
|
23
|
+
label: z.string(),
|
|
24
|
+
type: z.literal('boolean'),
|
|
25
|
+
defaultValue: z.boolean(),
|
|
26
|
+
// The line under the label in the picker: what turning it on costs or asks for.
|
|
27
|
+
description: z.string().optional()
|
|
28
|
+
})
|
|
29
|
+
]);
|
|
30
|
+
export type ModelOptionDescriptor = z.infer<typeof ModelOptionDescriptorSchema>;
|
|
31
|
+
|
|
32
|
+
export const ModelInfoSchema = z.object({
|
|
33
|
+
slug: z.string().min(1),
|
|
34
|
+
name: z.string(),
|
|
35
|
+
badge: z.string().optional(),
|
|
36
|
+
legacy: z.boolean(),
|
|
37
|
+
isDefault: z.boolean(),
|
|
38
|
+
options: z.array(ModelOptionDescriptorSchema)
|
|
39
|
+
});
|
|
40
|
+
export type ModelInfo = z.infer<typeof ModelInfoSchema>;
|
|
41
|
+
|
|
42
|
+
export const ModelSelectionSchema = z.object({
|
|
43
|
+
model: z.string().min(1),
|
|
44
|
+
options: z.record(z.string(), z.union([z.string(), z.boolean()]))
|
|
45
|
+
});
|
|
46
|
+
export type ModelSelection = z.infer<typeof ModelSelectionSchema>;
|
|
47
|
+
|
|
48
|
+
// A slug ends up on a CLI's command line, so it never starts with a dash and carries no brackets.
|
|
49
|
+
const ModelSlugSchema = z.string().regex(/^[a-z0-9][a-z0-9._-]*$/, 'Expected a model slug');
|
|
50
|
+
|
|
51
|
+
export const ModelCatalogProfileSchema = z.object({
|
|
52
|
+
options: z.array(ModelOptionDescriptorSchema),
|
|
53
|
+
// Context size per value of the `contextWindow` option, or `*` when the model has one size.
|
|
54
|
+
contextWindowTokens: z.record(z.string(), z.number().int().positive())
|
|
55
|
+
});
|
|
56
|
+
export type ModelCatalogProfile = z.infer<typeof ModelCatalogProfileSchema>;
|
|
57
|
+
|
|
58
|
+
export const ModelCatalogEntrySchema = z.object({
|
|
59
|
+
slug: ModelSlugSchema,
|
|
60
|
+
name: z.string().min(1),
|
|
61
|
+
badge: z.string().optional(),
|
|
62
|
+
profile: z.string().min(1),
|
|
63
|
+
aliases: z.array(z.string().min(1)).optional(),
|
|
64
|
+
legacy: z.boolean().optional()
|
|
65
|
+
});
|
|
66
|
+
export type ModelCatalogEntry = z.infer<typeof ModelCatalogEntrySchema>;
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* The models one provider offers, as a host ships them and as a catalog service hands out a newer
|
|
70
|
+
* copy. `updatedAt` orders the two, so a copy older than the one a host shipped with is never taken.
|
|
71
|
+
*/
|
|
72
|
+
export const ModelCatalogDataSchema = z
|
|
73
|
+
.object({
|
|
74
|
+
updatedAt: z.iso.datetime(),
|
|
75
|
+
defaultModel: ModelSlugSchema,
|
|
76
|
+
profiles: z.record(z.string(), ModelCatalogProfileSchema),
|
|
77
|
+
models: z.array(ModelCatalogEntrySchema).min(1)
|
|
78
|
+
})
|
|
79
|
+
.refine((data) => data.models.every((model) => data.profiles[model.profile] !== undefined), 'Every model needs a profile the catalog has')
|
|
80
|
+
.refine((data) => data.models.some((model) => model.slug === data.defaultModel), 'The default model has to be one of the models');
|
|
81
|
+
export type ModelCatalogData = z.infer<typeof ModelCatalogDataSchema>;
|
|
82
|
+
|
|
83
|
+
// The thread's permission policy, one vocabulary for every provider; each adapter maps it.
|
|
84
|
+
export const RuntimeModeSchema = z.enum(['supervised', 'auto-accept-edits', 'auto', 'full-access']);
|
|
85
|
+
export type RuntimeMode = z.infer<typeof RuntimeModeSchema>;
|
|
86
|
+
|
|
87
|
+
// What a provider's CLI can do, so a client never offers what it would drop and the daemon
|
|
88
|
+
// never asks for what the protocol has no room for. Read by the composer and the thread rows.
|
|
89
|
+
export const ProviderCapabilitiesSchema = z.object({
|
|
90
|
+
// Where this CLI can be opened: as a chat node, as a terminal node, or both.
|
|
91
|
+
chat: z.boolean(),
|
|
92
|
+
terminal: z.boolean(),
|
|
93
|
+
// Whether the daemon understands this CLI's hooks; without them a node shows the session status only.
|
|
94
|
+
hooks: z.boolean(),
|
|
95
|
+
// Partial output while a tool call runs, for the live row under it.
|
|
96
|
+
streamsToolOutput: z.boolean(),
|
|
97
|
+
// How a file change reaches the thread: as a unified diff, as the text before and after, or not at all.
|
|
98
|
+
diffs: z.enum(['unified', 'before-after', 'none']),
|
|
99
|
+
attachments: z.boolean(),
|
|
100
|
+
mentions: z.boolean(),
|
|
101
|
+
// Whether a decline carries a reason for the agent.
|
|
102
|
+
denyReason: z.boolean(),
|
|
103
|
+
allowAlways: z.boolean(),
|
|
104
|
+
// A question the person may leave alone while the turn goes on.
|
|
105
|
+
asyncQuestions: z.boolean(),
|
|
106
|
+
// Folding the context: a call of its own, a slash command sent as a turn, or nothing.
|
|
107
|
+
compaction: z.enum(['native', 'prompt', 'none']),
|
|
108
|
+
reportsCost: z.boolean(),
|
|
109
|
+
reportsContextWindow: z.boolean(),
|
|
110
|
+
// Whether the CLI hands over what the model thought before it answered.
|
|
111
|
+
reportsThinking: z.boolean(),
|
|
112
|
+
slashCommands: z.boolean()
|
|
113
|
+
});
|
|
114
|
+
export type ProviderCapabilities = z.infer<typeof ProviderCapabilitiesSchema>;
|
|
115
|
+
|
|
116
|
+
export const ProviderInfoSchema = z.object({
|
|
117
|
+
kind: AgentKindSchema,
|
|
118
|
+
name: z.string(),
|
|
119
|
+
installed: z.boolean(),
|
|
120
|
+
version: z.string().nullable(),
|
|
121
|
+
// Empty while the CLI is not installed or has no chat backend yet.
|
|
122
|
+
models: z.array(ModelInfoSchema),
|
|
123
|
+
defaultModel: z.string().nullable(),
|
|
124
|
+
capabilities: ProviderCapabilitiesSchema,
|
|
125
|
+
// What a terminal runs to continue one of this CLI's sessions. `{id}` stands for the session id
|
|
126
|
+
// and `{flags}` for the flags the launch carries, which is where in the line each CLI takes them.
|
|
127
|
+
resumeCommand: z.string()
|
|
128
|
+
});
|
|
129
|
+
export type ProviderInfo = z.infer<typeof ProviderInfoSchema>;
|
|
130
|
+
|
|
131
|
+
export const ProviderListResultSchema = z.object({
|
|
132
|
+
providers: z.array(ProviderInfoSchema)
|
|
133
|
+
});
|
|
134
|
+
export type ProviderListResult = z.infer<typeof ProviderListResultSchema>;
|
|
135
|
+
|
|
136
|
+
/*
|
|
137
|
+
* Fills a provider's resume template; the id is quoted so a shell takes it as one word. `{flags}` is
|
|
138
|
+
* a word of its own rather than a substring, since it stands for none, one or several words and a
|
|
139
|
+
* substitution would leave a double space behind where a launch carries no flags at all.
|
|
140
|
+
*/
|
|
141
|
+
export function resumeCommandFor(template: string, agentSessionId: string, flags: string[] = []): string {
|
|
142
|
+
const id = `'${agentSessionId.replaceAll("'", `'\\''`)}'`;
|
|
143
|
+
return template
|
|
144
|
+
.split(' ')
|
|
145
|
+
.flatMap((word) => (word === '{flags}' ? flags : [word.replace('{id}', id)]))
|
|
146
|
+
.join(' ');
|
|
147
|
+
}
|
package/src/port.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Request, ServerFrame } from './envelope.ts';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Two ends that pass the wire's frames without a socket: a MessagePort between a renderer and the
|
|
5
|
+
* process that runs the chats, or a pair in a test. A frame is checked where it arrives, so it
|
|
6
|
+
* crosses as `unknown`.
|
|
7
|
+
*/
|
|
8
|
+
export interface FramePort {
|
|
9
|
+
send(frame: Request | ServerFrame): void;
|
|
10
|
+
onFrame(listener: (frame: unknown) => void): () => void;
|
|
11
|
+
}
|
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import {
|
|
3
|
+
ChatAddBookmarkPayloadSchema,
|
|
4
|
+
ChatAnswerPayloadSchema,
|
|
5
|
+
ChatApprovePayloadSchema,
|
|
6
|
+
ChatAttachPayloadSchema,
|
|
7
|
+
ChatAttachResultSchema,
|
|
8
|
+
ChatBookmarksEventSchema,
|
|
9
|
+
ChatBookmarksResultSchema,
|
|
10
|
+
ChatCancelPayloadSchema,
|
|
11
|
+
ChatClearPayloadSchema,
|
|
12
|
+
ChatConfigurePayloadSchema,
|
|
13
|
+
ChatContinueOnPayloadSchema,
|
|
14
|
+
ChatContinueOnResultSchema,
|
|
15
|
+
ChatCreatePayloadSchema,
|
|
16
|
+
ChatDismissPayloadSchema,
|
|
17
|
+
ChatEventEnvelopeSchema,
|
|
18
|
+
ChatForkInfoPayloadSchema,
|
|
19
|
+
ChatForkInfoResultSchema,
|
|
20
|
+
ChatForkPayloadSchema,
|
|
21
|
+
ChatForkResultSchema,
|
|
22
|
+
ChatHistoryPayloadSchema,
|
|
23
|
+
ChatHistoryResultSchema,
|
|
24
|
+
ChatInfoSchema,
|
|
25
|
+
ChatListResultSchema,
|
|
26
|
+
ChatPreferencesPayloadSchema,
|
|
27
|
+
ChatQueuePayloadSchema,
|
|
28
|
+
ChatRemoveBookmarkPayloadSchema,
|
|
29
|
+
ChatRenameBookmarkPayloadSchema,
|
|
30
|
+
ChatSendPayloadSchema,
|
|
31
|
+
ChatSendResultSchema,
|
|
32
|
+
ChatStatusEventSchema,
|
|
33
|
+
ChatStopSubagentPayloadSchema,
|
|
34
|
+
ChatStopTaskPayloadSchema,
|
|
35
|
+
ChatSubagentChangedEventSchema,
|
|
36
|
+
ChatSubagentPayloadSchema,
|
|
37
|
+
ChatSubagentResultSchema,
|
|
38
|
+
ChatSummarizePayloadSchema,
|
|
39
|
+
ChatSummarizeResultSchema,
|
|
40
|
+
ChatTargetPayloadSchema,
|
|
41
|
+
ChatTurnDiffPayloadSchema,
|
|
42
|
+
ChatTurnDiffResultSchema,
|
|
43
|
+
ChatUnqueueResultSchema,
|
|
44
|
+
SkillsListPayloadSchema,
|
|
45
|
+
SkillsListResultSchema
|
|
46
|
+
} from './chat.ts';
|
|
47
|
+
import { ProviderListResultSchema } from './model.ts';
|
|
48
|
+
import {
|
|
49
|
+
ProviderAccountCreatePayloadSchema,
|
|
50
|
+
ProviderAccountCreateResultSchema,
|
|
51
|
+
ProviderAccountWatchLoginPayloadSchema,
|
|
52
|
+
ProviderAccountsSavePayloadSchema,
|
|
53
|
+
ProviderAccountsSchema
|
|
54
|
+
} from './provider-accounts.ts';
|
|
55
|
+
import { UsageChangedEventSchema, UsageLimitsSnapshotSchema, UsageSummaryPayloadSchema, UsageSummaryResultSchema } from './usage.ts';
|
|
56
|
+
|
|
57
|
+
export const EmptySchema = z.object({});
|
|
58
|
+
|
|
59
|
+
/*
|
|
60
|
+
* The requests a chat, its providers and their usage answer, with what goes in and what comes back.
|
|
61
|
+
* An application places each one in its own wire table, so a host that only runs chats serves
|
|
62
|
+
* exactly these and a client reads the same shapes either way.
|
|
63
|
+
*/
|
|
64
|
+
export const AGENT_REQUEST_SCHEMAS = {
|
|
65
|
+
'chat.create': { payload: ChatCreatePayloadSchema, result: ChatInfoSchema },
|
|
66
|
+
'chat.history': { payload: ChatHistoryPayloadSchema, result: ChatHistoryResultSchema },
|
|
67
|
+
'chat.attach': { payload: ChatAttachPayloadSchema, result: ChatAttachResultSchema },
|
|
68
|
+
'chat.detach': { payload: ChatTargetPayloadSchema, result: EmptySchema },
|
|
69
|
+
'chat.send': { payload: ChatSendPayloadSchema, result: ChatSendResultSchema },
|
|
70
|
+
'chat.unqueue': { payload: ChatQueuePayloadSchema, result: ChatUnqueueResultSchema },
|
|
71
|
+
'chat.sendNow': { payload: ChatQueuePayloadSchema, result: EmptySchema },
|
|
72
|
+
'chat.cancel': { payload: ChatCancelPayloadSchema, result: EmptySchema },
|
|
73
|
+
'chat.approve': { payload: ChatApprovePayloadSchema, result: EmptySchema },
|
|
74
|
+
'chat.answer': { payload: ChatAnswerPayloadSchema, result: EmptySchema },
|
|
75
|
+
'chat.dismiss': { payload: ChatDismissPayloadSchema, result: EmptySchema },
|
|
76
|
+
'chat.configure': { payload: ChatConfigurePayloadSchema, result: ChatInfoSchema },
|
|
77
|
+
'chat.setPreferences': { payload: ChatPreferencesPayloadSchema, result: EmptySchema },
|
|
78
|
+
'chat.compact': { payload: ChatTargetPayloadSchema, result: EmptySchema },
|
|
79
|
+
'chat.clear': { payload: ChatClearPayloadSchema, result: EmptySchema },
|
|
80
|
+
'chat.turnDiff': { payload: ChatTurnDiffPayloadSchema, result: ChatTurnDiffResultSchema },
|
|
81
|
+
'chat.fork': { payload: ChatForkPayloadSchema, result: ChatForkResultSchema },
|
|
82
|
+
'chat.continueOn': { payload: ChatContinueOnPayloadSchema, result: ChatContinueOnResultSchema },
|
|
83
|
+
'chat.forkInfo': { payload: ChatForkInfoPayloadSchema, result: ChatForkInfoResultSchema },
|
|
84
|
+
'chat.summarize': { payload: ChatSummarizePayloadSchema, result: ChatSummarizeResultSchema },
|
|
85
|
+
'chat.subagent': { payload: ChatSubagentPayloadSchema, result: ChatSubagentResultSchema },
|
|
86
|
+
'chat.stopSubagent': { payload: ChatStopSubagentPayloadSchema, result: EmptySchema },
|
|
87
|
+
'chat.stopTask': { payload: ChatStopTaskPayloadSchema, result: EmptySchema },
|
|
88
|
+
'chat.addBookmark': { payload: ChatAddBookmarkPayloadSchema, result: ChatBookmarksResultSchema },
|
|
89
|
+
'chat.renameBookmark': { payload: ChatRenameBookmarkPayloadSchema, result: ChatBookmarksResultSchema },
|
|
90
|
+
'chat.removeBookmark': { payload: ChatRemoveBookmarkPayloadSchema, result: ChatBookmarksResultSchema },
|
|
91
|
+
'skills.list': { payload: SkillsListPayloadSchema, result: SkillsListResultSchema },
|
|
92
|
+
'provider.list': { payload: EmptySchema, result: ProviderListResultSchema },
|
|
93
|
+
'accounts.list': { payload: EmptySchema, result: ProviderAccountsSchema },
|
|
94
|
+
'accounts.save': { payload: ProviderAccountsSavePayloadSchema, result: ProviderAccountsSchema },
|
|
95
|
+
// Asks every CLI again and answers once they all did.
|
|
96
|
+
'accounts.refresh': { payload: EmptySchema, result: ProviderAccountsSchema },
|
|
97
|
+
'accounts.create': { payload: ProviderAccountCreatePayloadSchema, result: ProviderAccountCreateResultSchema },
|
|
98
|
+
// Answers at once; what the CLI says arrives as `accounts.changed`.
|
|
99
|
+
'accounts.watchLogin': { payload: ProviderAccountWatchLoginPayloadSchema, result: EmptySchema },
|
|
100
|
+
'usage.summary': { payload: UsageSummaryPayloadSchema, result: UsageSummaryResultSchema },
|
|
101
|
+
'usage.subscribe': { payload: EmptySchema, result: EmptySchema },
|
|
102
|
+
'usage.unsubscribe': { payload: EmptySchema, result: EmptySchema },
|
|
103
|
+
'usage.limits': { payload: EmptySchema, result: UsageLimitsSnapshotSchema },
|
|
104
|
+
'usage.refreshLimits': { payload: EmptySchema, result: UsageLimitsSnapshotSchema },
|
|
105
|
+
'chat.kill': { payload: ChatTargetPayloadSchema, result: EmptySchema },
|
|
106
|
+
'chat.list': { payload: EmptySchema, result: ChatListResultSchema }
|
|
107
|
+
} as const;
|
|
108
|
+
|
|
109
|
+
export const AGENT_EVENT_SCHEMAS = {
|
|
110
|
+
'chat.event': ChatEventEnvelopeSchema,
|
|
111
|
+
'chat.status': ChatStatusEventSchema,
|
|
112
|
+
'chat.subagentChanged': ChatSubagentChangedEventSchema,
|
|
113
|
+
'chat.bookmarks': ChatBookmarksEventSchema,
|
|
114
|
+
'usage.changed': UsageChangedEventSchema,
|
|
115
|
+
'usage.limitsChanged': UsageLimitsSnapshotSchema,
|
|
116
|
+
'accounts.changed': ProviderAccountsSchema
|
|
117
|
+
} as const;
|
|
118
|
+
|
|
119
|
+
export type AgentRequestType = keyof typeof AGENT_REQUEST_SCHEMAS;
|
|
120
|
+
export type AgentEventType = keyof typeof AGENT_EVENT_SCHEMAS;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AgentKindSchema } from './agent.ts';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* An account of an agent CLI is one config folder of that CLI, handed to it through the variable it
|
|
6
|
+
* reads its folder from. The person signs in with the CLI itself; the daemon only asks the CLI who
|
|
7
|
+
* that is. Accounts belong to one host and live in its provider account store.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/* A slug, never a path. The default account of a CLI has the CLI's kind as its id. */
|
|
11
|
+
export const ProviderAccountIdSchema = z.string().regex(/^[a-z][a-z0-9_-]{0,63}$/);
|
|
12
|
+
export type ProviderAccountId = z.infer<typeof ProviderAccountIdSchema>;
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* A variable the CLI of an account is started with. A sensitive value lives in the keychain of the
|
|
16
|
+
* machine and never on the wire: the daemon sends it as an empty `value` with `valueRedacted`, and a
|
|
17
|
+
* save that sends that back keeps what the keychain holds.
|
|
18
|
+
*/
|
|
19
|
+
export const ProviderAccountVariableSchema = z.object({
|
|
20
|
+
name: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,127}$/),
|
|
21
|
+
value: z.string(),
|
|
22
|
+
sensitive: z.boolean(),
|
|
23
|
+
valueRedacted: z.boolean().optional()
|
|
24
|
+
});
|
|
25
|
+
export type ProviderAccountVariable = z.infer<typeof ProviderAccountVariableSchema>;
|
|
26
|
+
|
|
27
|
+
export const ProviderAccountSchema = z.object({
|
|
28
|
+
// An agent kind. A kind this version does not know still parses, so the entry is written back unchanged.
|
|
29
|
+
kind: z.string().min(1),
|
|
30
|
+
label: z.string().optional(),
|
|
31
|
+
// A node accent name (`NODE_ACCENT_NAMES`), kept a plain string so an older name is written back; the client paints any other with its accent.
|
|
32
|
+
color: z.string().optional(),
|
|
33
|
+
// Absent is on. An account that is off is left out of every picker.
|
|
34
|
+
enabled: z.boolean().optional(),
|
|
35
|
+
// The CLI's config folder, `~` allowed. Absent on the default account, which uses the CLI's own folder.
|
|
36
|
+
home: z.string().min(1).optional(),
|
|
37
|
+
// Codex only: a folder of its own for the login, sharing everything else with `home`.
|
|
38
|
+
shadowHome: z.string().min(1).optional(),
|
|
39
|
+
// Set on the CLI's environment after the login variables are taken off, so a key here is one a person chose.
|
|
40
|
+
env: z.array(ProviderAccountVariableSchema).optional()
|
|
41
|
+
});
|
|
42
|
+
export type ProviderAccount = z.infer<typeof ProviderAccountSchema>;
|
|
43
|
+
|
|
44
|
+
export const ProviderAccountStateSchema = z.enum([
|
|
45
|
+
'checking',
|
|
46
|
+
'disabled',
|
|
47
|
+
// The CLI is not installed on this machine.
|
|
48
|
+
'not-found',
|
|
49
|
+
'signed-out',
|
|
50
|
+
'ready',
|
|
51
|
+
'folder-missing',
|
|
52
|
+
// A kind this version does not know, or a CLI with no variable for its config folder.
|
|
53
|
+
'unavailable',
|
|
54
|
+
'failed'
|
|
55
|
+
]);
|
|
56
|
+
export type ProviderAccountState = z.infer<typeof ProviderAccountStateSchema>;
|
|
57
|
+
|
|
58
|
+
/* What the CLI said about an account the last time it was asked. Never stored: the CLI is the truth. */
|
|
59
|
+
export const ProviderAccountStatusSchema = z.object({
|
|
60
|
+
id: ProviderAccountIdSchema,
|
|
61
|
+
kind: z.string(),
|
|
62
|
+
state: ProviderAccountStateSchema,
|
|
63
|
+
email: z.string().nullable(),
|
|
64
|
+
plan: z.string().nullable(),
|
|
65
|
+
organization: z.string().nullable(),
|
|
66
|
+
// The folder the CLI is started with, resolved; empty for a CLI without a config folder variable.
|
|
67
|
+
home: z.string(),
|
|
68
|
+
// Where the account's conversations are written, resolved: a chat goes on under another account only when both write here.
|
|
69
|
+
transcripts: z.string().optional(),
|
|
70
|
+
// Why the state is what it is, in a sentence, when the CLI or the daemon said.
|
|
71
|
+
message: z.string().nullable(),
|
|
72
|
+
// Milliseconds since the epoch; 0 while the account was never checked.
|
|
73
|
+
checkedAt: z.number()
|
|
74
|
+
});
|
|
75
|
+
export type ProviderAccountStatus = z.infer<typeof ProviderAccountStatusSchema>;
|
|
76
|
+
|
|
77
|
+
export const ProviderAccountMapSchema = z.record(ProviderAccountIdSchema, ProviderAccountSchema);
|
|
78
|
+
export type ProviderAccountMap = z.infer<typeof ProviderAccountMapSchema>;
|
|
79
|
+
|
|
80
|
+
export const ProviderAccountsSchema = z.object({
|
|
81
|
+
accounts: ProviderAccountMapSchema,
|
|
82
|
+
statuses: z.array(ProviderAccountStatusSchema),
|
|
83
|
+
// Whether this machine can keep a sensitive variable; false off macOS, where no keychain is used.
|
|
84
|
+
secretsAvailable: z.boolean().optional(),
|
|
85
|
+
// Per agent kind, what a terminal types to sign that CLI in under an account, which the account's environment points at.
|
|
86
|
+
loginCommands: z.partialRecord(AgentKindSchema, z.string()).optional()
|
|
87
|
+
});
|
|
88
|
+
export type ProviderAccounts = z.infer<typeof ProviderAccountsSchema>;
|
|
89
|
+
|
|
90
|
+
/* The whole map: an id left out is removed, and its folder stays where it is. */
|
|
91
|
+
export const ProviderAccountsSavePayloadSchema = z.object({
|
|
92
|
+
accounts: ProviderAccountMapSchema
|
|
93
|
+
});
|
|
94
|
+
export type ProviderAccountsSavePayload = z.infer<typeof ProviderAccountsSavePayloadSchema>;
|
|
95
|
+
|
|
96
|
+
/* An account in a folder the daemon makes for it under its account data directory; a Codex one is a shadow home over the CLI's own folder. */
|
|
97
|
+
export const ProviderAccountCreatePayloadSchema = z.object({
|
|
98
|
+
kind: AgentKindSchema,
|
|
99
|
+
label: z.string().trim().min(1).max(80),
|
|
100
|
+
color: z.string().optional()
|
|
101
|
+
});
|
|
102
|
+
export type ProviderAccountCreatePayload = z.infer<typeof ProviderAccountCreatePayloadSchema>;
|
|
103
|
+
|
|
104
|
+
export const ProviderAccountCreateResultSchema = ProviderAccountsSchema.extend({
|
|
105
|
+
// The id the daemon minted from the label.
|
|
106
|
+
id: ProviderAccountIdSchema
|
|
107
|
+
});
|
|
108
|
+
export type ProviderAccountCreateResult = z.infer<typeof ProviderAccountCreateResultSchema>;
|
|
109
|
+
|
|
110
|
+
/* Asks the CLI of one account again every few seconds until it is signed in, for a login running in a terminal. */
|
|
111
|
+
export const ProviderAccountWatchLoginPayloadSchema = z.object({
|
|
112
|
+
id: ProviderAccountIdSchema
|
|
113
|
+
});
|
|
114
|
+
export type ProviderAccountWatchLoginPayload = z.infer<typeof ProviderAccountWatchLoginPayloadSchema>;
|
package/src/task.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const TaskStatusSchema = z.enum(['open', 'done', 'failed', 'cancelled']);
|
|
4
|
+
export type TaskStatus = z.infer<typeof TaskStatusSchema>;
|
|
5
|
+
|
|
6
|
+
export const TaskResultSchema = z.object({
|
|
7
|
+
text: z.string(),
|
|
8
|
+
// How the result came about: the child called `done`, its first turn ended, or its process left.
|
|
9
|
+
source: z.enum(['done', 'turn', 'exit']),
|
|
10
|
+
at: z.number()
|
|
11
|
+
});
|
|
12
|
+
export type TaskResult = z.infer<typeof TaskResultSchema>;
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* What a chat asked of a node it opened with `--task`, kept by the host under its data folder rather
|
|
16
|
+
* than anywhere an agent can write: waking the parent is a promise the host keeps.
|
|
17
|
+
*/
|
|
18
|
+
export const TaskSchema = z.object({
|
|
19
|
+
id: z.string().min(1),
|
|
20
|
+
projectId: z.string().min(1),
|
|
21
|
+
parentId: z.string().min(1),
|
|
22
|
+
childId: z.string().min(1),
|
|
23
|
+
title: z.string(),
|
|
24
|
+
prompt: z.string(),
|
|
25
|
+
// The one `team --task` call it came from: the parent is woken about that call once every task of it settled.
|
|
26
|
+
batchId: z.string().min(1).optional(),
|
|
27
|
+
requiresTaskTurn: z.boolean().optional(),
|
|
28
|
+
background: z.object({ turnId: z.string(), itemIds: z.array(z.string()), commands: z.array(z.string()) }).optional(),
|
|
29
|
+
status: TaskStatusSchema,
|
|
30
|
+
result: TaskResultSchema.nullable(),
|
|
31
|
+
createdAt: z.number(),
|
|
32
|
+
settledAt: z.number().nullable(),
|
|
33
|
+
// Whether the parent has been woken about this task yet; `none` for a task that never wakes anybody.
|
|
34
|
+
wake: z.enum(['pending', 'sent', 'none']),
|
|
35
|
+
// Set while the child's last turn stopped on a limit: the task stays open and wakes nobody, `until` the limit lifts when known.
|
|
36
|
+
paused: z.object({ kind: z.enum(['usage', 'overload']), until: z.number().optional() }).optional()
|
|
37
|
+
});
|
|
38
|
+
export type Task = z.infer<typeof TaskSchema>;
|
|
39
|
+
|
|
40
|
+
export const TaskChangedEventSchema = z.object({ task: TaskSchema });
|
|
41
|
+
export type TaskChangedEvent = z.infer<typeof TaskChangedEventSchema>;
|
package/src/text.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const graphemes = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* The longest start of `text` that fits in `max` UTF-16 units, the measure a schema's `max` counts in,
|
|
5
|
+
* cut only between characters as a person sees them. A cut between the halves of a surrogate pair
|
|
6
|
+
* leaves a lone half, which JSON writes as an escape the iPhone app refuses along with its whole frame.
|
|
7
|
+
*/
|
|
8
|
+
export function clipText(text: string, max: number): string {
|
|
9
|
+
if (text.length <= max) {
|
|
10
|
+
return text;
|
|
11
|
+
}
|
|
12
|
+
let end = 0;
|
|
13
|
+
for (const { segment } of graphemes.segment(text)) {
|
|
14
|
+
if (end + segment.length > max) {
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
end += segment.length;
|
|
18
|
+
}
|
|
19
|
+
return text.slice(0, end);
|
|
20
|
+
}
|