@jancellor/ask 1.0.0 → 1.1.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/README.md +87 -7
- package/dist/agent/agent.d.ts +38 -0
- package/dist/agent/agent.js +20 -18
- package/dist/agent/agents-prompt.d.ts +4 -0
- package/dist/agent/check.d.ts +1 -0
- package/dist/agent/check.js +5 -0
- package/dist/agent/config-schema.d.ts +56 -0
- package/dist/agent/config-schema.js +26 -0
- package/dist/agent/config-store.d.ts +12 -0
- package/dist/agent/config-store.js +46 -0
- package/dist/agent/config.d.ts +27 -0
- package/dist/agent/config.js +84 -46
- package/dist/agent/execute-tool.d.ts +15 -0
- package/dist/agent/execute-tool.js +1 -3
- package/dist/agent/fs-errors.js +23 -0
- package/dist/agent/fs-ops.d.ts +2 -0
- package/dist/agent/fs-ops.js +14 -0
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/init-prompt.d.ts +3 -0
- package/dist/agent/messages.d.ts +10 -0
- package/dist/agent/openai-subscription-fetch.d.ts +5 -0
- package/dist/agent/openai-subscription-fetch.js +34 -34
- package/dist/agent/paths.d.ts +1 -0
- package/dist/agent/provider-factories.d.ts +9 -0
- package/dist/agent/provider-factories.js +21 -0
- package/dist/agent/serializer.d.ts +6 -0
- package/dist/agent/serializer.js +4 -6
- package/dist/agent/session-store.d.ts +18 -0
- package/dist/agent/session-store.js +4 -12
- package/dist/agent/session.d.ts +18 -0
- package/dist/agent/session.js +1 -1
- package/dist/agent/skills-prompt.d.ts +6 -0
- package/dist/agent/system-prompt.d.ts +3 -0
- package/dist/agent/tools.d.ts +6 -0
- package/dist/batch/index.d.ts +4 -0
- package/dist/batch/index.js +1 -1
- package/dist/batch/run.d.ts +4 -0
- package/dist/batch/run.js +101 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +56 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.js +22 -0
- package/dist/config/run.d.ts +6 -0
- package/dist/config/run.js +22 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -32
- package/dist/interactive/app.d.ts +7 -0
- package/dist/interactive/app.js +12 -0
- package/dist/interactive/assistant-part-message.d.ts +4 -0
- package/dist/interactive/assistant-part-message.js +10 -0
- package/dist/interactive/execute-tool-part-message.d.ts +2 -0
- package/dist/interactive/execute-tool-part-message.js +48 -0
- package/dist/interactive/generic-tool-part-message.d.ts +2 -0
- package/dist/interactive/generic-tool-part-message.js +28 -0
- package/dist/interactive/input.d.ts +8 -0
- package/dist/interactive/input.js +115 -0
- package/dist/interactive/markdown.d.ts +1 -0
- package/dist/interactive/markdown.js +31 -0
- package/dist/interactive/messages.d.ts +9 -0
- package/dist/interactive/messages.js +87 -0
- package/dist/interactive/run.d.ts +4 -0
- package/dist/interactive/run.js +18 -0
- package/dist/interactive/spinner-message.d.ts +1 -0
- package/dist/interactive/spinner-message.js +14 -0
- package/dist/interactive/tool-part-message.d.ts +12 -0
- package/dist/interactive/tool-part-message.js +9 -0
- package/dist/interactive/use-agent.d.ts +13 -0
- package/dist/interactive/use-agent.js +27 -0
- package/dist/interactive/use-input-state.d.ts +19 -0
- package/dist/interactive/use-input-state.js +136 -0
- package/dist/interactive/user-part-message.d.ts +3 -0
- package/dist/interactive/user-part-message.js +9 -0
- package/dist/interactive/welcome.d.ts +7 -0
- package/dist/interactive/welcome.js +12 -0
- package/dist/shutdown-manager.d.ts +8 -0
- package/dist/tui/app.d.ts +7 -0
- package/dist/tui/app.js +2 -2
- package/dist/tui/assistant-part-message.d.ts +4 -0
- package/dist/tui/assistant-part-message.js +2 -2
- package/dist/tui/execute-tool-part-message.d.ts +2 -0
- package/dist/tui/execute-tool-part-message.js +1 -1
- package/dist/tui/generic-tool-part-message.d.ts +2 -0
- package/dist/tui/generic-tool-part-message.js +1 -1
- package/dist/tui/index.d.ts +4 -0
- package/dist/tui/input.d.ts +8 -0
- package/dist/tui/input.js +1 -1
- package/dist/tui/markdown.d.ts +1 -0
- package/dist/tui/messages.d.ts +9 -0
- package/dist/tui/messages.js +4 -3
- package/dist/tui/run.d.ts +4 -0
- package/dist/tui/run.js +18 -0
- package/dist/tui/spinner-message.d.ts +1 -0
- package/dist/tui/tool-part-message.d.ts +12 -0
- package/dist/tui/tool-part-message.js +3 -3
- package/dist/tui/use-agent.d.ts +13 -0
- package/dist/tui/use-agent.js +3 -3
- package/dist/tui/use-input-state.d.ts +19 -0
- package/dist/tui/user-part-message.d.ts +3 -0
- package/dist/tui/welcome.d.ts +7 -0
- package/dist/tui/welcome.js +3 -3
- package/package.json +17 -5
package/README.md
CHANGED
|
@@ -50,24 +50,104 @@ npm link
|
|
|
50
50
|
Configure a provider:
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
|
|
54
|
-
export ASK_MODEL="anthropic/claude-sonnet-4.6"
|
|
55
|
-
export ASK_BASE_URL="https://openrouter.ai/api/v1"
|
|
53
|
+
ask --config --provider anthropic --model claude-opus-4-6
|
|
56
54
|
```
|
|
57
55
|
|
|
58
|
-
|
|
56
|
+
That saves the current config and creates `~/.config/ask/config.json`, eg:
|
|
59
57
|
|
|
60
58
|
```json
|
|
61
59
|
{
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
|
|
60
|
+
"currentProvider": "anthropic",
|
|
61
|
+
"providers": {
|
|
62
|
+
"anthropic": {
|
|
63
|
+
"currentModel": "claude-opus-4-6"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
65
66
|
}
|
|
66
67
|
```
|
|
67
68
|
|
|
69
|
+
Store secrets separately in `~/.config/ask/config.secrets.json`:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"anthropic": {
|
|
74
|
+
"apiKey": "your-api-key"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
For OpenAI-compatible endpoints, configure the provider explicitly:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"currentProvider": "openrouter",
|
|
84
|
+
"providers": {
|
|
85
|
+
"openrouter": {
|
|
86
|
+
"sdkProvider": "openai-compatible",
|
|
87
|
+
"providerOptions": {
|
|
88
|
+
"name": "openrouter",
|
|
89
|
+
"baseURL": "https://openrouter.ai/api/v1"
|
|
90
|
+
},
|
|
91
|
+
"currentModel": "anthropic/claude-sonnet-4.6"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Ask uses the [AI SDK](https://ai-sdk.dev/docs/reference/ai-sdk-core), and this
|
|
98
|
+
config is designed to map directly onto that runtime model. You select a
|
|
99
|
+
configured provider, then a model within that provider, and optionally a
|
|
100
|
+
variant within that model. A configured provider can also override
|
|
101
|
+
[`sdkProvider`](https://ai-sdk.dev/docs/providers) so one named config entry can
|
|
102
|
+
target a different SDK provider family, such as `openai-compatible`.
|
|
103
|
+
`providerOptions` live on the provider and are passed to the SDK provider
|
|
104
|
+
factory, while auth stays separate in `config.secrets.json`. `generateOptions`
|
|
105
|
+
can be set globally and at the provider, model, and variant levels; at runtime
|
|
106
|
+
they are merged in that order and passed through as the options object for
|
|
107
|
+
[`generateText`](https://ai-sdk.dev/docs/ai-sdk-core/generating-text).
|
|
108
|
+
|
|
109
|
+
For example, you can add a Claude reasoning-effort variant with per-variant
|
|
110
|
+
Anthropic options:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"currentProvider": "anthropic",
|
|
115
|
+
"providers": {
|
|
116
|
+
"anthropic": {
|
|
117
|
+
"currentModel": "claude-opus-4-6",
|
|
118
|
+
"models": {
|
|
119
|
+
"claude-opus-4-6": {
|
|
120
|
+
"currentVariant": "balanced",
|
|
121
|
+
"variants": {
|
|
122
|
+
"balanced": {
|
|
123
|
+
"generateOptions": {
|
|
124
|
+
"providerOptions": {
|
|
125
|
+
"anthropic": {
|
|
126
|
+
"effort": "medium"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Then use `ask -v balanced` to select that variant for a run.
|
|
140
|
+
|
|
141
|
+
Use `ask -c` to print the resolved config. If you pass `-p`, `-m`, or `-v`
|
|
142
|
+
with `-c`, those values are saved as the new current selection. Without `-c`,
|
|
143
|
+
they apply only to the current run.
|
|
144
|
+
|
|
68
145
|
Run:
|
|
69
146
|
|
|
70
147
|
```bash
|
|
148
|
+
ask -c # Show current resolved config
|
|
149
|
+
ask -c -p openai -m gpt-5 # Update saved provider/model
|
|
150
|
+
ask -c -v # Clear the saved variant
|
|
71
151
|
ask # Interactive mode
|
|
72
152
|
ask "refactor" # Batch mode (single positional arg)
|
|
73
153
|
cat file.ts | ask "explain" # Pipe context, ask a question
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ConfigOptions } from './config.js';
|
|
2
|
+
import type { AskMessage } from './messages.js';
|
|
3
|
+
import { type SessionOptions } from './session.js';
|
|
4
|
+
export type { AskMessage, AskMessageMeta } from './messages.js';
|
|
5
|
+
export interface AgentListener {
|
|
6
|
+
onMessages?(messages: AskMessage[]): void | Promise<void>;
|
|
7
|
+
onClear?(): void | Promise<void>;
|
|
8
|
+
onFork?(): void | Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export type AgentOptions = SessionOptions & ConfigOptions;
|
|
11
|
+
export declare const ABORTED_MESSAGE = "[Aborted]";
|
|
12
|
+
export declare const ERROR_MESSAGE = "[Error]";
|
|
13
|
+
export declare class Agent {
|
|
14
|
+
private listeners;
|
|
15
|
+
private config;
|
|
16
|
+
private systemPrompt;
|
|
17
|
+
private tools;
|
|
18
|
+
private session;
|
|
19
|
+
private serializer;
|
|
20
|
+
private controller;
|
|
21
|
+
private constructor();
|
|
22
|
+
static create(options: AgentOptions): Promise<Agent>;
|
|
23
|
+
addListener(listener: AgentListener): void;
|
|
24
|
+
removeListener(listener: AgentListener): void;
|
|
25
|
+
get messages(): AskMessage[];
|
|
26
|
+
get sessionId(): string;
|
|
27
|
+
get model(): string;
|
|
28
|
+
get provider(): string;
|
|
29
|
+
get variant(): string | null;
|
|
30
|
+
ask(message: string): Promise<void>;
|
|
31
|
+
abort(): void;
|
|
32
|
+
cancelAll(): Promise<void>;
|
|
33
|
+
clear(beforeClear?: () => void): Promise<void>;
|
|
34
|
+
fork(sessionId?: string, beforeFork?: () => void): Promise<void>;
|
|
35
|
+
private addMessages;
|
|
36
|
+
private addInitialMessages;
|
|
37
|
+
private callTools;
|
|
38
|
+
}
|
package/dist/agent/agent.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
2
1
|
import { generateText, } from 'ai';
|
|
3
|
-
import { ConfigReader } from './config.js';
|
|
2
|
+
import { ConfigReader, } from './config.js';
|
|
4
3
|
import { InitPrompt } from './init-prompt.js';
|
|
5
4
|
import { Session } from './session.js';
|
|
6
5
|
import { SystemPrompt } from './system-prompt.js';
|
|
@@ -9,32 +8,25 @@ import { Tools } from './tools.js';
|
|
|
9
8
|
export const ABORTED_MESSAGE = '[Aborted]';
|
|
10
9
|
export const ERROR_MESSAGE = '[Error]';
|
|
11
10
|
export class Agent {
|
|
12
|
-
modelId;
|
|
13
|
-
baseUrl;
|
|
14
11
|
listeners = [];
|
|
15
|
-
|
|
12
|
+
config;
|
|
16
13
|
systemPrompt;
|
|
17
14
|
tools;
|
|
18
15
|
session;
|
|
19
16
|
serializer = new Serializer();
|
|
20
17
|
controller = null;
|
|
21
|
-
constructor(session) {
|
|
18
|
+
constructor(session, config) {
|
|
22
19
|
this.session = session;
|
|
23
|
-
|
|
24
|
-
this.modelId = config.model;
|
|
25
|
-
this.baseUrl = config.baseUrl;
|
|
26
|
-
const provider = createOpenAICompatible({
|
|
27
|
-
name: 'ask',
|
|
28
|
-
apiKey: config.apiKey,
|
|
29
|
-
baseURL: config.baseUrl,
|
|
30
|
-
});
|
|
31
|
-
this.languageModel = provider(config.model);
|
|
20
|
+
this.config = config;
|
|
32
21
|
this.systemPrompt = new SystemPrompt().build();
|
|
33
22
|
this.tools = new Tools();
|
|
34
23
|
}
|
|
35
24
|
static async create(options) {
|
|
36
|
-
const session = await
|
|
37
|
-
|
|
25
|
+
const [session, config] = await Promise.all([
|
|
26
|
+
Session.create(options),
|
|
27
|
+
new ConfigReader().resolve(options),
|
|
28
|
+
]);
|
|
29
|
+
return new Agent(session, config);
|
|
38
30
|
}
|
|
39
31
|
addListener(listener) {
|
|
40
32
|
this.listeners.push(listener);
|
|
@@ -50,6 +42,15 @@ export class Agent {
|
|
|
50
42
|
get sessionId() {
|
|
51
43
|
return this.session.sessionId;
|
|
52
44
|
}
|
|
45
|
+
get model() {
|
|
46
|
+
return this.config.model;
|
|
47
|
+
}
|
|
48
|
+
get provider() {
|
|
49
|
+
return this.config.provider;
|
|
50
|
+
}
|
|
51
|
+
get variant() {
|
|
52
|
+
return this.config.variant;
|
|
53
|
+
}
|
|
53
54
|
ask(message) {
|
|
54
55
|
return this.serializer.submit(async () => {
|
|
55
56
|
await this.addInitialMessages();
|
|
@@ -59,7 +60,8 @@ export class Agent {
|
|
|
59
60
|
try {
|
|
60
61
|
while (true) {
|
|
61
62
|
const result = await generateText({
|
|
62
|
-
|
|
63
|
+
...this.config.generateOptions,
|
|
64
|
+
model: this.config.languageModel,
|
|
63
65
|
system: this.systemPrompt,
|
|
64
66
|
messages: this.session.messages,
|
|
65
67
|
tools: this.tools.definitions(),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function check(condition: unknown, message: string): asserts condition;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const GenerateOptions: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
3
|
+
export declare const ProviderOptions: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
4
|
+
export declare const ProviderSecretOptions: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
5
|
+
export declare const VariantConfig: z.ZodObject<{
|
|
6
|
+
generateOptions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
export declare const ModelConfig: z.ZodObject<{
|
|
9
|
+
sdkModel: z.ZodOptional<z.ZodString>;
|
|
10
|
+
generateOptions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
11
|
+
currentVariant: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
12
|
+
variants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
13
|
+
generateOptions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
14
|
+
}, z.core.$strip>>>;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
export declare const ProviderConfig: z.ZodObject<{
|
|
17
|
+
sdkProvider: z.ZodOptional<z.ZodString>;
|
|
18
|
+
providerOptions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
19
|
+
generateOptions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
20
|
+
currentModel: z.ZodOptional<z.ZodString>;
|
|
21
|
+
models: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
22
|
+
sdkModel: z.ZodOptional<z.ZodString>;
|
|
23
|
+
generateOptions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
24
|
+
currentVariant: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
25
|
+
variants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
26
|
+
generateOptions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
27
|
+
}, z.core.$strip>>>;
|
|
28
|
+
}, z.core.$strip>>>;
|
|
29
|
+
}, z.core.$strip>;
|
|
30
|
+
export declare const Config: z.ZodObject<{
|
|
31
|
+
currentProvider: z.ZodOptional<z.ZodString>;
|
|
32
|
+
generateOptions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
33
|
+
providers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
34
|
+
sdkProvider: z.ZodOptional<z.ZodString>;
|
|
35
|
+
providerOptions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
36
|
+
generateOptions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
37
|
+
currentModel: z.ZodOptional<z.ZodString>;
|
|
38
|
+
models: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
39
|
+
sdkModel: z.ZodOptional<z.ZodString>;
|
|
40
|
+
generateOptions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
41
|
+
currentVariant: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
42
|
+
variants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
43
|
+
generateOptions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
44
|
+
}, z.core.$strip>>>;
|
|
45
|
+
}, z.core.$strip>>>;
|
|
46
|
+
}, z.core.$strip>>>;
|
|
47
|
+
}, z.core.$strip>;
|
|
48
|
+
export declare const ConfigSecrets: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
49
|
+
export type GenerateOptions = z.infer<typeof GenerateOptions>;
|
|
50
|
+
export type ProviderOptions = z.infer<typeof ProviderOptions>;
|
|
51
|
+
export type ProviderSecretOptions = z.infer<typeof ProviderSecretOptions>;
|
|
52
|
+
export type Config = z.infer<typeof Config>;
|
|
53
|
+
export type ProviderConfig = z.infer<typeof ProviderConfig>;
|
|
54
|
+
export type ModelConfig = z.infer<typeof ModelConfig>;
|
|
55
|
+
export type VariantConfig = z.infer<typeof VariantConfig>;
|
|
56
|
+
export type ConfigSecrets = z.infer<typeof ConfigSecrets>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const GenerateOptions = z.record(z.string(), z.unknown());
|
|
3
|
+
export const ProviderOptions = z.record(z.string(), z.unknown());
|
|
4
|
+
export const ProviderSecretOptions = z.record(z.string(), z.unknown());
|
|
5
|
+
export const VariantConfig = z.object({
|
|
6
|
+
generateOptions: GenerateOptions.optional(),
|
|
7
|
+
});
|
|
8
|
+
export const ModelConfig = z.object({
|
|
9
|
+
sdkModel: z.string().optional(),
|
|
10
|
+
generateOptions: GenerateOptions.optional(),
|
|
11
|
+
currentVariant: z.string().nullable().optional(),
|
|
12
|
+
variants: z.record(z.string(), VariantConfig).optional(),
|
|
13
|
+
});
|
|
14
|
+
export const ProviderConfig = z.object({
|
|
15
|
+
sdkProvider: z.string().optional(),
|
|
16
|
+
providerOptions: ProviderOptions.optional(),
|
|
17
|
+
generateOptions: GenerateOptions.optional(),
|
|
18
|
+
currentModel: z.string().optional(),
|
|
19
|
+
models: z.record(z.string(), ModelConfig).optional(),
|
|
20
|
+
});
|
|
21
|
+
export const Config = z.object({
|
|
22
|
+
currentProvider: z.string().optional(),
|
|
23
|
+
generateOptions: GenerateOptions.optional(),
|
|
24
|
+
providers: z.record(z.string(), ProviderConfig).optional(),
|
|
25
|
+
});
|
|
26
|
+
export const ConfigSecrets = z.record(z.string(), ProviderSecretOptions);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ConfigSecrets, type Config as ConfigType } from './config-schema.js';
|
|
2
|
+
export declare class ConfigStore {
|
|
3
|
+
private static CONFIG_DIR;
|
|
4
|
+
private static CONFIG_PATH;
|
|
5
|
+
private static SECRETS_PATH;
|
|
6
|
+
readConfig(): Promise<ConfigType>;
|
|
7
|
+
readSecrets(): Promise<ConfigSecrets>;
|
|
8
|
+
writeConfig(config: ConfigType): Promise<void>;
|
|
9
|
+
private readJsonFile;
|
|
10
|
+
private parseWithSchema;
|
|
11
|
+
private writeConfigAtomically;
|
|
12
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from 'fs/promises';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { Config, ConfigSecrets, } from './config-schema.js';
|
|
5
|
+
import { ignoreMissing } from './fs-ops.js';
|
|
6
|
+
export class ConfigStore {
|
|
7
|
+
static CONFIG_DIR = join(homedir(), '.config', 'ask');
|
|
8
|
+
static CONFIG_PATH = join(ConfigStore.CONFIG_DIR, 'config.json');
|
|
9
|
+
static SECRETS_PATH = join(ConfigStore.CONFIG_DIR, 'config.secrets.json');
|
|
10
|
+
async readConfig() {
|
|
11
|
+
const raw = await this.readJsonFile(ConfigStore.CONFIG_PATH);
|
|
12
|
+
return this.parseWithSchema('config.json', Config, raw ?? {});
|
|
13
|
+
}
|
|
14
|
+
async readSecrets() {
|
|
15
|
+
const raw = await this.readJsonFile(ConfigStore.SECRETS_PATH);
|
|
16
|
+
return this.parseWithSchema('config.secrets.json', ConfigSecrets, raw ?? {});
|
|
17
|
+
}
|
|
18
|
+
async writeConfig(config) {
|
|
19
|
+
const serialized = JSON.stringify(config, null, 2) + '\n';
|
|
20
|
+
await this.writeConfigAtomically(serialized);
|
|
21
|
+
}
|
|
22
|
+
async readJsonFile(path) {
|
|
23
|
+
const content = await ignoreMissing(() => readFile(path, 'utf-8'));
|
|
24
|
+
if (content === undefined)
|
|
25
|
+
return undefined;
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(content);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
throw new Error(`invalid JSON in ${path}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
parseWithSchema(schemaName, parser, input) {
|
|
34
|
+
const result = parser.safeParse(input);
|
|
35
|
+
if (result.success)
|
|
36
|
+
return result.data;
|
|
37
|
+
throw new Error(`invalid ${schemaName}: ${JSON.stringify(result.error.issues, null, 2)}`);
|
|
38
|
+
}
|
|
39
|
+
async writeConfigAtomically(data) {
|
|
40
|
+
await mkdir(ConfigStore.CONFIG_DIR, { recursive: true });
|
|
41
|
+
const path = ConfigStore.CONFIG_PATH;
|
|
42
|
+
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
43
|
+
await writeFile(tempPath, data, 'utf-8');
|
|
44
|
+
await rename(tempPath, path);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { LanguageModel } from 'ai';
|
|
2
|
+
import { type GenerateOptions, type ProviderOptions } from './config-schema.js';
|
|
3
|
+
export type ConfigOptions = {
|
|
4
|
+
provider?: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
variant?: string | null;
|
|
7
|
+
saveAsCurrent?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type ResolvedConfig = {
|
|
10
|
+
provider: string;
|
|
11
|
+
model: string;
|
|
12
|
+
variant: string | null;
|
|
13
|
+
sdkProvider: string;
|
|
14
|
+
sdkModel: string;
|
|
15
|
+
providerOptions: ProviderOptions;
|
|
16
|
+
generateOptions: GenerateOptions;
|
|
17
|
+
languageModel: LanguageModel;
|
|
18
|
+
};
|
|
19
|
+
export declare class ConfigReader {
|
|
20
|
+
private store;
|
|
21
|
+
constructor();
|
|
22
|
+
resolve(configOptions: ConfigOptions): Promise<ResolvedConfig>;
|
|
23
|
+
private saveAsCurrent;
|
|
24
|
+
private resolveProvider;
|
|
25
|
+
private resolveModel;
|
|
26
|
+
private resolveVariant;
|
|
27
|
+
}
|
package/dist/agent/config.js
CHANGED
|
@@ -1,54 +1,92 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { isMatch, merge } from 'lodash-es';
|
|
2
|
+
import { check } from './check.js';
|
|
3
|
+
import { ConfigStore } from './config-store.js';
|
|
4
|
+
import { createLanguageModel } from './provider-factories.js';
|
|
4
5
|
export class ConfigReader {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return JSON.parse(readFileSync(ConfigReader.CONFIG_PATH, 'utf-8'));
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
return {};
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
envOrFile(envKey, fileKey, fileValues) {
|
|
15
|
-
const envValue = process.env[envKey];
|
|
16
|
-
if (envValue)
|
|
17
|
-
return envValue.trim();
|
|
18
|
-
const fileValue = fileValues[fileKey];
|
|
19
|
-
if (fileValue != null)
|
|
20
|
-
return String(fileValue).trim();
|
|
21
|
-
throw new Error(`neither ${envKey} nor ${fileKey} is set`);
|
|
22
|
-
}
|
|
23
|
-
optionalEnvOrFile(envKey, fileKey, fileValues) {
|
|
24
|
-
const envValue = process.env[envKey];
|
|
25
|
-
if (envValue && envValue.trim())
|
|
26
|
-
return envValue.trim();
|
|
27
|
-
const fileValue = fileValues[fileKey];
|
|
28
|
-
if (fileValue == null)
|
|
29
|
-
return undefined;
|
|
30
|
-
const value = String(fileValue).trim();
|
|
31
|
-
return value ? value : undefined;
|
|
6
|
+
store;
|
|
7
|
+
constructor() {
|
|
8
|
+
this.store = new ConfigStore();
|
|
32
9
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
10
|
+
async resolve(configOptions) {
|
|
11
|
+
const [config, secrets] = await Promise.all([
|
|
12
|
+
this.store.readConfig(),
|
|
13
|
+
this.store.readSecrets(),
|
|
14
|
+
]);
|
|
15
|
+
const { provider, providerConfig } = this.resolveProvider(config, configOptions);
|
|
16
|
+
const { model, modelConfig } = this.resolveModel(providerConfig, configOptions);
|
|
17
|
+
const { variant, variantConfig } = this.resolveVariant(modelConfig, configOptions);
|
|
18
|
+
const sdkProvider = providerConfig.sdkProvider ?? provider;
|
|
19
|
+
const sdkModel = modelConfig.sdkModel ?? model;
|
|
20
|
+
const providerOptions = providerConfig.providerOptions ?? {};
|
|
21
|
+
const providerSecretOptions = secrets[provider] ?? {};
|
|
22
|
+
if (configOptions.saveAsCurrent) {
|
|
23
|
+
await this.saveAsCurrent(config, provider, model, variant);
|
|
39
24
|
}
|
|
25
|
+
const generateOptions = merge({}, config.generateOptions, providerConfig.generateOptions, modelConfig.generateOptions, variantConfig?.generateOptions);
|
|
26
|
+
const languageModel = createLanguageModel({
|
|
27
|
+
sdkProvider,
|
|
28
|
+
sdkModel,
|
|
29
|
+
providerOptions,
|
|
30
|
+
providerSecretOptions,
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
provider,
|
|
34
|
+
model,
|
|
35
|
+
variant,
|
|
36
|
+
sdkProvider,
|
|
37
|
+
sdkModel,
|
|
38
|
+
providerOptions,
|
|
39
|
+
generateOptions,
|
|
40
|
+
languageModel,
|
|
41
|
+
};
|
|
40
42
|
}
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
async saveAsCurrent(config, provider, model, variant) {
|
|
44
|
+
const persistedVariant = config.providers?.[provider]?.models?.[model]?.currentVariant ?? null;
|
|
45
|
+
// prevent unnecessary config only to set `currentVariant: null`
|
|
46
|
+
const shouldOmitNullVariant = variant === null && persistedVariant === null;
|
|
47
|
+
const patch = {
|
|
48
|
+
currentProvider: provider,
|
|
49
|
+
providers: {
|
|
50
|
+
[provider]: {
|
|
51
|
+
currentModel: model,
|
|
52
|
+
...(!shouldOmitNullVariant
|
|
53
|
+
? {
|
|
54
|
+
models: {
|
|
55
|
+
[model]: {
|
|
56
|
+
currentVariant: variant,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
: {}),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
if (!isMatch(config, patch)) {
|
|
65
|
+
const patched = merge({}, config, patch);
|
|
66
|
+
await this.store.writeConfig(patched);
|
|
47
67
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
68
|
+
}
|
|
69
|
+
resolveProvider(config, configOptions) {
|
|
70
|
+
const provider = configOptions.provider ?? config.currentProvider;
|
|
71
|
+
check(provider, 'provider not specified');
|
|
72
|
+
const providerConfig = config.providers?.[provider] ?? {
|
|
73
|
+
sdkProvider: provider,
|
|
52
74
|
};
|
|
75
|
+
return { provider, providerConfig };
|
|
76
|
+
}
|
|
77
|
+
resolveModel(provider, configOptions) {
|
|
78
|
+
const model = configOptions.model ?? provider.currentModel;
|
|
79
|
+
check(model, `model not specified`);
|
|
80
|
+
const modelConfig = provider.models?.[model] ?? {
|
|
81
|
+
sdkModel: model,
|
|
82
|
+
};
|
|
83
|
+
return { model, modelConfig };
|
|
84
|
+
}
|
|
85
|
+
resolveVariant(model, configOptions) {
|
|
86
|
+
const variant = configOptions.variant ?? model.currentVariant ?? null;
|
|
87
|
+
const variantConfig = variant ? model.variants?.[variant] : undefined;
|
|
88
|
+
if (variant)
|
|
89
|
+
check(variantConfig, `variant not found: ${variant}`);
|
|
90
|
+
return { variant, variantConfig };
|
|
53
91
|
}
|
|
54
92
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type ExecuteToolOutput = {
|
|
2
|
+
exit?: number;
|
|
3
|
+
signal?: string;
|
|
4
|
+
stdout?: string;
|
|
5
|
+
stderr?: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare class ExecuteTool {
|
|
9
|
+
readonly name = "execute";
|
|
10
|
+
definition(): import("ai").Tool<{
|
|
11
|
+
command: string;
|
|
12
|
+
}, never>;
|
|
13
|
+
execute(input: unknown, signal: AbortSignal): Promise<ExecuteToolOutput>;
|
|
14
|
+
private signalProcessGroup;
|
|
15
|
+
}
|
|
@@ -5,9 +5,7 @@ import { z } from 'zod';
|
|
|
5
5
|
const DEFAULT_TIMEOUT_S = 60;
|
|
6
6
|
const TERMINATION_GRACE_MS = 5000;
|
|
7
7
|
const executeInputSchema = z.object({
|
|
8
|
-
command: z
|
|
9
|
-
.string()
|
|
10
|
-
.describe('The shell command to execute using `bash -c`'),
|
|
8
|
+
command: z.string().describe('The shell command to execute using `bash -c`'),
|
|
11
9
|
});
|
|
12
10
|
export class ExecuteTool {
|
|
13
11
|
name = 'execute';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function isEnoentError(error) {
|
|
2
|
+
if (!error || typeof error !== 'object')
|
|
3
|
+
return false;
|
|
4
|
+
return 'code' in error && error.code === 'ENOENT';
|
|
5
|
+
}
|
|
6
|
+
export async function ignoreMissing(op) {
|
|
7
|
+
try {
|
|
8
|
+
return await op();
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (!isEnoentError(error))
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function ignoreMissingSync(op) {
|
|
16
|
+
try {
|
|
17
|
+
return op();
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (!isEnoentError(error))
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function isEnoentError(error) {
|
|
2
|
+
if (!error || typeof error !== 'object')
|
|
3
|
+
return false;
|
|
4
|
+
return 'code' in error && error.code === 'ENOENT';
|
|
5
|
+
}
|
|
6
|
+
export async function ignoreMissing(op) {
|
|
7
|
+
try {
|
|
8
|
+
return await op();
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (!isEnoentError(error))
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Agent, ABORTED_MESSAGE, ERROR_MESSAGE, type AskMessage, type AgentOptions, type AskMessageMeta, type AgentListener, } from './agent.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* I don't think this works but leaving it plugged in for now.
|
|
3
|
+
* We might need to set other headers/content in order to use subscriptions.
|
|
4
|
+
*/
|
|
5
|
+
export declare function createOpenAISubscriptionFetch(): (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|