@fastagent-sh/fastagent 0.14.0 → 0.16.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 +16 -6
- package/dist/agent.d.ts +20 -0
- package/dist/agent.js +9 -0
- package/dist/channels/agentcore-limits.d.ts +7 -0
- package/dist/channels/agentcore-limits.js +9 -0
- package/dist/channels/agentcore-state.d.ts +83 -0
- package/dist/channels/agentcore-state.js +258 -0
- package/dist/channels/agentcore.d.ts +98 -0
- package/dist/channels/agentcore.js +260 -0
- package/dist/channels/busy.d.ts +23 -0
- package/dist/channels/busy.js +53 -0
- package/dist/channels/context-buffer.d.ts +35 -0
- package/dist/channels/context-buffer.js +103 -0
- package/dist/channels/control.d.ts +28 -0
- package/dist/channels/control.js +214 -0
- package/dist/channels/feishu/card.js +1 -1
- package/dist/channels/feishu/context-buffer.d.ts +15 -13
- package/dist/channels/feishu/context-buffer.js +62 -69
- package/dist/channels/feishu/feishu-api.d.ts +11 -3
- package/dist/channels/feishu/feishu-api.js +24 -3
- package/dist/channels/feishu/feishu.d.ts +27 -27
- package/dist/channels/feishu/feishu.js +318 -207
- package/dist/channels/feishu/invoke-turn.d.ts +7 -20
- package/dist/channels/feishu/invoke-turn.js +44 -79
- package/dist/channels/feishu/model.d.ts +2 -2
- package/dist/channels/feishu/normalize.d.ts +2 -1
- package/dist/channels/feishu/normalize.js +0 -1
- package/dist/channels/feishu/parse.d.ts +22 -8
- package/dist/channels/feishu/parse.js +24 -7
- package/dist/channels/feishu/preview.d.ts +4 -7
- package/dist/channels/feishu/preview.js +27 -142
- package/dist/channels/feishu/register-app.d.ts +2 -1
- package/dist/channels/feishu/register-webhook.d.ts +2 -1
- package/dist/channels/feishu/scaffold/channel.ts +9 -8
- package/dist/channels/feishu/scaffold/feishu-send.ts +6 -4
- package/dist/channels/feishu/setup-mode.d.ts +37 -0
- package/dist/channels/feishu/setup-mode.js +28 -0
- package/dist/channels/feishu/ws-ingress.d.ts +28 -0
- package/dist/channels/feishu/ws-ingress.js +136 -0
- package/dist/channels/github/github.js +16 -7
- package/dist/channels/http.d.ts +14 -0
- package/dist/channels/http.js +36 -3
- package/dist/channels/invoke-turn-kit.d.ts +65 -0
- package/dist/channels/invoke-turn-kit.js +87 -0
- package/dist/channels/lark/lark.d.ts +4 -2
- package/dist/channels/lark/lark.js +4 -1
- package/dist/channels/lark/onboard.d.ts +8 -4
- package/dist/channels/lark/onboard.js +8 -0
- package/dist/channels/lark/scaffold/channel.ts +9 -8
- package/dist/channels/lark/scaffold/lark-send.ts +6 -4
- package/dist/channels/preview-kit.d.ts +115 -0
- package/dist/channels/preview-kit.js +184 -0
- package/dist/channels/seen.d.ts +5 -0
- package/dist/channels/seen.js +35 -0
- package/dist/channels/slack/bot-auth.d.ts +15 -0
- package/dist/channels/slack/bot-auth.js +146 -0
- package/dist/channels/slack/config-api.d.ts +60 -0
- package/dist/channels/slack/config-api.js +149 -0
- package/dist/channels/slack/context-buffer.d.ts +24 -0
- package/dist/channels/slack/context-buffer.js +37 -0
- package/dist/channels/slack/invoke-turn.d.ts +19 -0
- package/dist/channels/slack/invoke-turn.js +63 -0
- package/dist/channels/slack/manifest.d.ts +49 -0
- package/dist/channels/slack/manifest.js +69 -0
- package/dist/channels/slack/model.d.ts +67 -0
- package/dist/channels/slack/model.js +2 -0
- package/dist/channels/slack/onboard.d.ts +41 -0
- package/dist/channels/slack/onboard.js +120 -0
- package/dist/channels/slack/onboarding-state.d.ts +31 -0
- package/dist/channels/slack/onboarding-state.js +69 -0
- package/dist/channels/slack/parse.d.ts +38 -0
- package/dist/channels/slack/parse.js +124 -0
- package/dist/channels/slack/preview.d.ts +23 -0
- package/dist/channels/slack/preview.js +403 -0
- package/dist/channels/slack/reaction.d.ts +24 -0
- package/dist/channels/slack/reaction.js +62 -0
- package/dist/channels/slack/register-webhook.d.ts +10 -0
- package/dist/channels/slack/register-webhook.js +49 -0
- package/dist/channels/slack/scaffold/channel.ts +33 -0
- package/dist/channels/slack/scaffold/slack-send.ts +171 -0
- package/dist/channels/slack/setup-server.d.ts +17 -0
- package/dist/channels/slack/setup-server.js +103 -0
- package/dist/channels/slack/slack-api.d.ts +57 -0
- package/dist/channels/slack/slack-api.js +399 -0
- package/dist/channels/slack/slack.d.ts +51 -0
- package/dist/channels/slack/slack.js +496 -0
- package/dist/channels/slack/welcomed.d.ts +5 -0
- package/dist/channels/slack/welcomed.js +32 -0
- package/dist/channels/state.d.ts +11 -4
- package/dist/channels/state.js +22 -15
- package/dist/channels/stop-command.d.ts +6 -0
- package/dist/channels/stop-command.js +36 -0
- package/dist/channels/tasks.d.ts +7 -0
- package/dist/channels/tasks.js +25 -0
- package/dist/channels/telegram/context-buffer.d.ts +8 -17
- package/dist/channels/telegram/context-buffer.js +6 -85
- package/dist/channels/telegram/invoke-turn.d.ts +5 -22
- package/dist/channels/telegram/invoke-turn.js +11 -58
- package/dist/channels/telegram/parse.d.ts +0 -7
- package/dist/channels/telegram/parse.js +4 -2
- package/dist/channels/telegram/preview.d.ts +4 -7
- package/dist/channels/telegram/preview.js +24 -142
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -3
- package/dist/channels/telegram/telegram.js +24 -10
- package/dist/channels/{feishu/text.d.ts → text.d.ts} +15 -1
- package/dist/channels/{feishu/text.js → text.js} +15 -1
- package/dist/channels/thread-participants.d.ts +21 -0
- package/dist/channels/thread-participants.js +132 -0
- package/dist/channels/turn-queue.js +8 -1
- package/dist/channels/turn-store.d.ts +1 -1
- package/dist/channels/turn-store.js +2 -3
- package/dist/cli/add-feishu.d.ts +30 -0
- package/dist/cli/add-feishu.js +366 -0
- package/dist/cli/add-slack.d.ts +11 -0
- package/dist/cli/add-slack.js +199 -0
- package/dist/{cli-auth.d.ts → cli/auth-view.d.ts} +1 -1
- package/dist/{cli-auth.js → cli/auth-view.js} +1 -1
- package/dist/cli/commands/add.d.ts +4 -0
- package/dist/cli/commands/add.js +191 -60
- package/dist/cli/commands/attach.d.ts +82 -0
- package/dist/cli/commands/attach.js +563 -0
- package/dist/cli/commands/chat.d.ts +1 -0
- package/dist/cli/commands/chat.js +12 -7
- package/dist/cli/commands/deploy.d.ts +3 -1
- package/dist/cli/commands/deploy.js +384 -82
- package/dist/cli/commands/dev.js +34 -26
- package/dist/cli/commands/fire.js +17 -18
- package/dist/cli/commands/info.js +36 -29
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.js +65 -53
- package/dist/cli/commands/invoke.js +11 -9
- package/dist/cli/commands/login.js +48 -45
- package/dist/cli/commands/models.js +1 -1
- package/dist/cli/commands/schedule.js +6 -8
- package/dist/cli/commands/start.js +102 -39
- package/dist/cli/commands/tool.js +23 -18
- package/dist/cli/fail.d.ts +17 -2
- package/dist/cli/fail.js +25 -1
- package/dist/{invoke-stream.d.ts → cli/invoke-stream.d.ts} +1 -1
- package/dist/{invoke-stream.js → cli/invoke-stream.js} +4 -0
- package/dist/cli/kernel.d.ts +3 -2
- package/dist/cli/models-view.d.ts +21 -0
- package/dist/cli/models-view.js +66 -0
- package/dist/cli/program.js +135 -56
- package/dist/cli/serve.d.ts +60 -20
- package/dist/cli/serve.js +252 -47
- package/dist/cli/shared.d.ts +40 -7
- package/dist/cli/shared.js +180 -38
- package/dist/collect.d.ts +14 -3
- package/dist/collect.js +24 -0
- package/dist/core.d.ts +3 -1
- package/dist/core.js +2 -0
- package/dist/deploy/agentcore/plan.d.ts +117 -0
- package/dist/deploy/agentcore/plan.js +721 -0
- package/dist/deploy/agentcore/run.d.ts +73 -0
- package/dist/deploy/agentcore/run.js +412 -0
- package/dist/deploy/agentcore/zip.d.ts +17 -0
- package/dist/deploy/agentcore/zip.js +68 -0
- package/dist/deploy/container.d.ts +26 -25
- package/dist/deploy/container.js +93 -89
- package/dist/deploy/docker/plan.d.ts +3 -3
- package/dist/deploy/docker/plan.js +20 -23
- package/dist/deploy/fly/plan.d.ts +5 -1
- package/dist/deploy/fly/plan.js +39 -26
- package/dist/deploy/fly/run.d.ts +17 -5
- package/dist/deploy/fly/run.js +52 -7
- package/dist/deploy/preflight.d.ts +20 -8
- package/dist/deploy/preflight.js +267 -71
- package/dist/deploy/railway/plan.d.ts +10 -1
- package/dist/deploy/railway/plan.js +53 -20
- package/dist/deploy/railway/run.d.ts +13 -5
- package/dist/deploy/railway/run.js +22 -7
- package/dist/deploy/runner.d.ts +7 -3
- package/dist/deploy/runner.js +9 -3
- package/dist/deploy/secrets.d.ts +2 -1
- package/dist/deploy/secrets.js +23 -3
- package/dist/dev-supervisor.d.ts +11 -10
- package/dist/dev-supervisor.js +56 -50
- package/dist/engines/pi/auth.d.ts +8 -7
- package/dist/engines/pi/auth.js +12 -10
- package/dist/engines/pi/channel.d.ts +22 -16
- package/dist/engines/pi/channel.js +92 -62
- package/dist/engines/pi/chat.d.ts +4 -16
- package/dist/engines/pi/chat.js +8 -261
- package/dist/engines/pi/config.d.ts +20 -55
- package/dist/engines/pi/config.js +49 -121
- package/dist/engines/pi/create.d.ts +30 -10
- package/dist/engines/pi/create.js +33 -26
- package/dist/engines/pi/definition.d.ts +7 -26
- package/dist/engines/pi/definition.js +8 -54
- package/dist/engines/pi/harness.d.ts +49 -0
- package/dist/engines/pi/harness.js +90 -2
- package/dist/engines/pi/invoke.d.ts +55 -2
- package/dist/engines/pi/invoke.js +304 -19
- package/dist/engines/pi/login.d.ts +12 -1
- package/dist/engines/pi/login.js +16 -4
- package/dist/engines/pi/models.d.ts +59 -13
- package/dist/engines/pi/models.js +62 -24
- package/dist/engines/pi/open.d.ts +114 -0
- package/dist/engines/pi/open.js +127 -0
- package/dist/engines/pi/session-builder.d.ts +16 -0
- package/dist/engines/pi/session-builder.js +308 -0
- package/dist/engines/pi/session-control.d.ts +50 -0
- package/dist/engines/pi/session-control.js +604 -0
- package/dist/engines/pi/sessions.d.ts +17 -2
- package/dist/engines/pi/sessions.js +9 -0
- package/dist/engines/pi/tool-context.d.ts +18 -11
- package/dist/engines/pi/tool-context.js +3 -9
- package/dist/engines/pi/tool.d.ts +6 -8
- package/dist/engines/pi/tool.js +10 -1
- package/dist/engines/pi/wake-tool.d.ts +0 -3
- package/dist/engines/pi/wake-tool.js +9 -7
- package/dist/env.d.ts +16 -4
- package/dist/env.js +43 -5
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/host/node.d.ts +25 -8
- package/dist/host/node.js +5 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/loader.d.ts +2 -2
- package/dist/loader.js +3 -3
- package/dist/log.d.ts +1 -1
- package/dist/log.js +1 -1
- package/dist/observe.js +3 -0
- package/dist/paths.d.ts +138 -0
- package/dist/paths.js +326 -0
- package/dist/pi.d.ts +6 -5
- package/dist/pi.js +4 -3
- package/dist/runtime.d.ts +7 -5
- package/dist/runtime.js +2 -2
- package/dist/scaffold/add-channel.d.ts +26 -9
- package/dist/scaffold/add-channel.js +175 -40
- package/dist/scaffold/init.d.ts +32 -41
- package/dist/scaffold/init.js +161 -185
- package/dist/scaffold/templates/env.example +15 -6
- package/dist/scaffold/templates/fastagent.config.mjs +5 -4
- package/dist/scaffold/templates/gitignore +14 -6
- package/dist/scaffold/templates/persona.md +4 -2
- package/dist/scaffold/templates/secrets.gitignore +5 -0
- package/dist/scaffold/templates.d.ts +1 -7
- package/dist/scaffold/templates.js +3 -25
- package/dist/scaffold/vendor-skill.d.ts +2 -2
- package/dist/scaffold/vendor-skill.js +13 -13
- package/dist/schedule/discover.js +4 -4
- package/dist/schedule/scheduler.d.ts +40 -1
- package/dist/schedule/scheduler.js +89 -56
- package/dist/schedule/state.js +1 -1
- package/dist/schedule/wake-alarm.d.ts +47 -0
- package/dist/schedule/wake-alarm.js +136 -0
- package/dist/schedule/wakeups.d.ts +1 -3
- package/dist/schedule/wakeups.js +19 -1
- package/dist/session-remote.d.ts +53 -0
- package/dist/session-remote.js +336 -0
- package/dist/session.d.ts +265 -0
- package/dist/session.js +37 -0
- package/dist/slack.d.ts +2 -0
- package/dist/slack.js +2 -0
- package/dist/tunnel.d.ts +7 -4
- package/dist/tunnel.js +23 -12
- package/package.json +22 -7
- package/dist/channels/feishu/owned-threads.d.ts +0 -11
- package/dist/channels/feishu/owned-threads.js +0 -47
- package/dist/channels/feishu/seen.d.ts +0 -5
- package/dist/channels/feishu/seen.js +0 -47
- package/dist/cli-add-feishu.d.ts +0 -8
- package/dist/cli-add-feishu.js +0 -223
- package/dist/cli-models.d.ts +0 -11
- package/dist/cli-models.js +0 -20
- package/dist/engines/pi/workspace.d.ts +0 -56
- package/dist/engines/pi/workspace.js +0 -72
- package/dist/scaffold/templates/gitignore.agentdir-root +0 -5
- package/dist/scaffold/templates/gitignore.kit +0 -2
- package/dist/workspace.d.ts +0 -9
- package/dist/workspace.js +0 -45
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_COMPACTION_SETTINGS, calculateContextTokens, shouldCompact } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import { SESSION_BUSY_CODE } from "../../agent.js";
|
|
2
|
+
import { ABORTED_CODE, SESSION_BUSY_CODE, } from "../../agent.js";
|
|
3
|
+
import { abortFirstIterator } from "../../collect.js";
|
|
3
4
|
import { log } from "../../log.js";
|
|
4
5
|
import { TOOL_ACTIVATION_ENTRY, harnessSession } from "./harness.js";
|
|
5
6
|
import { additiveActivation, turnContext } from "./tool-context.js";
|
|
@@ -95,21 +96,109 @@ function messageSignal(message) {
|
|
|
95
96
|
}
|
|
96
97
|
return {};
|
|
97
98
|
}
|
|
98
|
-
/**
|
|
99
|
-
|
|
99
|
+
/**
|
|
100
|
+
* In-stream event mapping — pi events are translated ONCE into the rich `SessionEvent` vocabulary;
|
|
101
|
+
* the SPEC `AgentEvent` stream is a narrow {@link projectAgentEvent} of it (design §6: one
|
|
102
|
+
* translation plus one projection, never two parallel translations). pi events with no session
|
|
103
|
+
* vocabulary yet (turn_start, agent_start, …) are dropped.
|
|
104
|
+
*/
|
|
105
|
+
export function toSessionEvent(pe, runId) {
|
|
106
|
+
const at = Date.now();
|
|
100
107
|
switch (pe.type) {
|
|
108
|
+
case "queue_update":
|
|
109
|
+
return {
|
|
110
|
+
type: "queue_changed",
|
|
111
|
+
timestamp: at,
|
|
112
|
+
runId,
|
|
113
|
+
data: { steering: pe.steer.length, followUp: pe.followUp.length },
|
|
114
|
+
};
|
|
115
|
+
case "message_start":
|
|
116
|
+
// Assistant streaming only — a user/toolResult message is not a live message boundary.
|
|
117
|
+
if (pe.message.role !== "assistant")
|
|
118
|
+
return null;
|
|
119
|
+
return { type: "message_started", timestamp: at, runId, data: {} };
|
|
101
120
|
case "message_update": {
|
|
102
121
|
const ev = pe.assistantMessageEvent;
|
|
103
|
-
if (ev.type === "text_delta")
|
|
104
|
-
return { type: "text", delta: ev.delta };
|
|
105
|
-
|
|
106
|
-
|
|
122
|
+
if (ev.type === "text_delta") {
|
|
123
|
+
return { type: "message_delta", timestamp: at, runId, data: { channel: "text", delta: ev.delta } };
|
|
124
|
+
}
|
|
125
|
+
if (ev.type === "thinking_delta") {
|
|
126
|
+
return { type: "message_delta", timestamp: at, runId, data: { channel: "thinking", delta: ev.delta } };
|
|
127
|
+
}
|
|
107
128
|
return null;
|
|
108
129
|
}
|
|
130
|
+
case "message_end":
|
|
131
|
+
if (pe.message.role !== "assistant")
|
|
132
|
+
return null;
|
|
133
|
+
return { type: "message_finished", timestamp: at, runId, data: {} };
|
|
109
134
|
case "tool_execution_start":
|
|
110
|
-
return {
|
|
135
|
+
return {
|
|
136
|
+
type: "tool_started",
|
|
137
|
+
timestamp: at,
|
|
138
|
+
runId,
|
|
139
|
+
data: { id: pe.toolCallId, name: pe.toolName, args: pe.args },
|
|
140
|
+
};
|
|
141
|
+
case "tool_execution_update":
|
|
142
|
+
return {
|
|
143
|
+
type: "tool_progress",
|
|
144
|
+
timestamp: at,
|
|
145
|
+
runId,
|
|
146
|
+
data: { id: pe.toolCallId, name: pe.toolName, partialResult: pe.partialResult },
|
|
147
|
+
};
|
|
111
148
|
case "tool_execution_end":
|
|
112
|
-
return {
|
|
149
|
+
return {
|
|
150
|
+
type: "tool_finished",
|
|
151
|
+
timestamp: at,
|
|
152
|
+
runId,
|
|
153
|
+
data: { id: pe.toolCallId, isError: pe.isError, content: pe.result },
|
|
154
|
+
};
|
|
155
|
+
case "retry_scheduled": {
|
|
156
|
+
// Summarization retry backoff (auto-compaction / branch summary, pi ≥0.81.1) — without it,
|
|
157
|
+
// up to ~14s of backoff at the turn's tail reads as a hang. `retry_attempt_start`/
|
|
158
|
+
// `retry_finished` stay dropped: they carry no outcome, and the next event is the closure.
|
|
159
|
+
// Typed against the vocabulary (as is the second construction site, session-control's
|
|
160
|
+
// manual-compact callback) so a payload change breaks both at compile time.
|
|
161
|
+
const event = {
|
|
162
|
+
type: "retry_scheduled",
|
|
163
|
+
timestamp: at,
|
|
164
|
+
runId,
|
|
165
|
+
data: {
|
|
166
|
+
operation: pe.operation,
|
|
167
|
+
attempt: pe.attempt,
|
|
168
|
+
maxAttempts: pe.maxAttempts,
|
|
169
|
+
delayMs: pe.delayMs,
|
|
170
|
+
error: pe.errorMessage,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
return event;
|
|
174
|
+
}
|
|
175
|
+
default:
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** The SPEC projection of the rich stream. Events with no `AgentEvent` counterpart (progress,
|
|
180
|
+
* message boundaries, run boundaries) project to null — the invoke terminal is produced from the
|
|
181
|
+
* resolved message ({@link toTerminal}), not from `run_settled`. */
|
|
182
|
+
export function projectAgentEvent(se) {
|
|
183
|
+
switch (se.type) {
|
|
184
|
+
case "message_delta": {
|
|
185
|
+
const d = se.data;
|
|
186
|
+
return d.channel === "text" ? { type: "text", delta: d.delta } : { type: "thinking", delta: d.delta };
|
|
187
|
+
}
|
|
188
|
+
case "tool_started": {
|
|
189
|
+
const d = se.data;
|
|
190
|
+
return { type: "tool_started", id: d.id, name: d.name, args: d.args };
|
|
191
|
+
}
|
|
192
|
+
case "tool_finished": {
|
|
193
|
+
const d = se.data;
|
|
194
|
+
return { type: "tool_ended", id: d.id, isError: d.isError, content: d.content };
|
|
195
|
+
}
|
|
196
|
+
case "retry_scheduled": {
|
|
197
|
+
// `operation` (compaction | branch_summary) stays session-plane vocabulary — a turn renderer
|
|
198
|
+
// only needs "transient failure, retrying"; the engine detail lives in the control plane.
|
|
199
|
+
const d = se.data;
|
|
200
|
+
return { type: "retrying", attempt: d.attempt, maxAttempts: d.maxAttempts, delayMs: d.delayMs, reason: d.error };
|
|
201
|
+
}
|
|
113
202
|
default:
|
|
114
203
|
return null;
|
|
115
204
|
}
|
|
@@ -120,7 +209,13 @@ function toAgentEvent(pe) {
|
|
|
120
209
|
* entire failure class (violating SPEC MUST 1).
|
|
121
210
|
*/
|
|
122
211
|
export function toTerminal(message) {
|
|
123
|
-
if (message.stopReason === "
|
|
212
|
+
if (message.stopReason === "aborted") {
|
|
213
|
+
// A deliberate stop (control-plane abort / harness abort), not an error — see {@link ABORTED_CODE}
|
|
214
|
+
// for the consumer contract (design §6).
|
|
215
|
+
const details = message.errorMessage ?? "run aborted";
|
|
216
|
+
return { type: "failed", details, retryable: false, code: ABORTED_CODE };
|
|
217
|
+
}
|
|
218
|
+
if (message.stopReason === "error") {
|
|
124
219
|
const details = message.errorMessage ?? `model stopped: ${message.stopReason}`;
|
|
125
220
|
return { type: "failed", details, retryable: classifyRetryable(details, messageSignal(message)) };
|
|
126
221
|
}
|
|
@@ -130,6 +225,20 @@ export function errorToTerminal(error) {
|
|
|
130
225
|
const details = error instanceof Error ? error.message : String(error);
|
|
131
226
|
return { type: "failed", details, retryable: classifyRetryable(details, errorSignal(error)) };
|
|
132
227
|
}
|
|
228
|
+
/** Bind the concrete pi-agent-core Session behind FastAgent's tool-runtime manager port. */
|
|
229
|
+
function toolSessionManager(sessionId, harness) {
|
|
230
|
+
const session = harnessSession(harness);
|
|
231
|
+
if (!session)
|
|
232
|
+
return undefined;
|
|
233
|
+
return {
|
|
234
|
+
getSessionId: () => sessionId,
|
|
235
|
+
async getHeader() {
|
|
236
|
+
const metadata = await session.getMetadata();
|
|
237
|
+
return { id: sessionId, timestamp: metadata.createdAt };
|
|
238
|
+
},
|
|
239
|
+
getBranch: () => session.getBranch(),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
133
242
|
/**
|
|
134
243
|
* The turn's {@link ToolActivation} over the live harness. `activate` is additive and filters to the
|
|
135
244
|
* registered names first — pi's `setActiveTools` THROWS on unknown names, and a loader must get a
|
|
@@ -243,10 +352,39 @@ class EventQueue {
|
|
|
243
352
|
}
|
|
244
353
|
/** "From a harness factory": engine wired by the caller; adds only the concurrency/stream shell. */
|
|
245
354
|
export function createPiAgentFromHarness(options) {
|
|
246
|
-
const { harnessFactory, lease = inProcessLease() } = options;
|
|
247
|
-
|
|
355
|
+
const { harnessFactory, lease = inProcessLease(), observer } = options;
|
|
356
|
+
function invoke(scope, prompt) {
|
|
357
|
+
// The cancellation DOOR (SPEC MUST 3), via the shared abort-first protocol (see
|
|
358
|
+
// abortFirstIterator): cancel aborts the engine work, which settles the run and releases a
|
|
359
|
+
// generator suspended on a quiet stream (a tool mid-execution). The local for-await pattern
|
|
360
|
+
// never hit the underlying deadlock (it breaks at a yield boundary); pull-driven consumers
|
|
361
|
+
// (the SSE handler's eager reads) do.
|
|
362
|
+
// The cancel intent is LATCHED: a consumer may cancel while the generator is still inside
|
|
363
|
+
// the harness build (the door not yet armed) — abortFirstIterator knocks exactly once, and a
|
|
364
|
+
// knock before prompt() starts would be a no-op on an idle harness (the LATER run would
|
|
365
|
+
// ignore it). So turn consults the latch right after arming and, when the consumer already
|
|
366
|
+
// walked away, never starts the model call at all.
|
|
367
|
+
let externalCancel;
|
|
368
|
+
let cancelled = false;
|
|
369
|
+
const gen = turn(scope, prompt, (cancel) => {
|
|
370
|
+
externalCancel = cancel;
|
|
371
|
+
}, () => cancelled);
|
|
372
|
+
const iterator = abortFirstIterator(gen, () => {
|
|
373
|
+
cancelled = true;
|
|
374
|
+
externalCancel?.();
|
|
375
|
+
});
|
|
376
|
+
return {
|
|
377
|
+
[Symbol.asyncIterator]() {
|
|
378
|
+
return iterator;
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
async function* turn(scope, prompt, onCancelReady,
|
|
383
|
+
/** The consumer's cancel latch (see invoke's wrapper) — checked once at arming. */
|
|
384
|
+
wasCancelled) {
|
|
248
385
|
const release = lease.tryAcquire(scope.session);
|
|
249
386
|
if (!release) {
|
|
387
|
+
// Rejected BEFORE acceptance: no run exists, so the observer sees nothing (replay-safe).
|
|
250
388
|
yield {
|
|
251
389
|
type: "failed",
|
|
252
390
|
details: "session busy: a turn is already in flight for this session",
|
|
@@ -255,6 +393,90 @@ export function createPiAgentFromHarness(options) {
|
|
|
255
393
|
};
|
|
256
394
|
return;
|
|
257
395
|
}
|
|
396
|
+
// The run exists from here: one run_started, exactly one run_settled. Terminal points only
|
|
397
|
+
// RECORD the outcome; the settlement event is emitted in the outer finally, right before
|
|
398
|
+
// release() — so the observation plane's "running" window equals the lease window (state()
|
|
399
|
+
// must never say idle while a new invoke would still be rejected session_busy), and the
|
|
400
|
+
// post-terminal auto-compaction is naturally inside the run. A run with no recorded outcome
|
|
401
|
+
// was cancelled by the caller (SPEC: cancellation has no terminal event) → aborted.
|
|
402
|
+
const runId = crypto.randomUUID();
|
|
403
|
+
let outcome;
|
|
404
|
+
const observe = (event, run) => {
|
|
405
|
+
if (!event || !observer)
|
|
406
|
+
return;
|
|
407
|
+
try {
|
|
408
|
+
observer(scope.session, event, run);
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
// The observation plane must never break the data plane; a broken hub is its own problem.
|
|
412
|
+
log.warn(`[fastagent] session observer threw (event ${event.type}): ${String(error)}`);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
// run_started must be observed before the (awaited) harness build so no early event outruns
|
|
416
|
+
// registration — so the controls AWAIT the build instead of erroring on the assembling window:
|
|
417
|
+
// a dispatch that races the build simply queues on the freshly built harness. A setup failure
|
|
418
|
+
// rejects the gate (and the run settles failed); the guard keeps an undispatched rejection from
|
|
419
|
+
// becoming an unhandled-rejection crash.
|
|
420
|
+
let harnessReady;
|
|
421
|
+
let harnessFailed;
|
|
422
|
+
const harnessGate = new Promise((resolve, reject) => {
|
|
423
|
+
harnessReady = resolve;
|
|
424
|
+
harnessFailed = reject;
|
|
425
|
+
});
|
|
426
|
+
harnessGate.catch(() => { }); // observed via controls only when a dispatch actually happens
|
|
427
|
+
// Aborted classification has two attribution sources, either suffices: pi's own
|
|
428
|
+
// stopReason:"aborted" (toTerminal), and control-plane INTENT — needed because providers do
|
|
429
|
+
// not uniformly attribute an aborted stream (verified empirically: the faux path surfaces a
|
|
430
|
+
// plain error). Intent = "an abort() succeeded, OR one was still in flight when the terminal
|
|
431
|
+
// arrived" (the harness error often lands before abort() resolves). A rejected abort that
|
|
432
|
+
// RETURNED before the terminal counts as nothing — no rollback dance, no interleaving hazard.
|
|
433
|
+
// GUARANTEE BOUNDARY: an abort still in flight that ultimately rejects can classify a
|
|
434
|
+
// concurrent real error as aborted — narrow, and non-lossy: the settlement carries
|
|
435
|
+
// `error.message` either way.
|
|
436
|
+
let abortsInFlight = 0;
|
|
437
|
+
let abortSucceeded = false;
|
|
438
|
+
// Stale-controls guard: after settlement pi's steer()/followUp()/abort() would still resolve
|
|
439
|
+
// (they queue / no-op on the to-be-discarded harness) — a silent acceptance of a command that
|
|
440
|
+
// can never take effect. The flag flips at THREE points, earliest wins: (1) the moment the
|
|
441
|
+
// run's terminal is determined (the main window — before the consumer-paced `yield terminal`
|
|
442
|
+
// and auto-compaction), (2) the setup-failure path, (3) the outer finally as the backstop for
|
|
443
|
+
// caller cancellation. A post-settle call throws and the dispatcher maps it to
|
|
444
|
+
// `run_command_failed`.
|
|
445
|
+
let runSettled = false;
|
|
446
|
+
const settledError = () => new Error("run already settled; the command cannot take effect");
|
|
447
|
+
// The settled check and the harness call MUST share one synchronous block (no await between):
|
|
448
|
+
// pi enqueues/aborts synchronously at method entry, so check-then-call in the same tick truly
|
|
449
|
+
// closes the race — a check behind its own await boundary would only shrink it.
|
|
450
|
+
const controls = {
|
|
451
|
+
async steer(p) {
|
|
452
|
+
const opts = await toPiPromptOptions(p);
|
|
453
|
+
const harness = await harnessGate;
|
|
454
|
+
if (runSettled)
|
|
455
|
+
throw settledError();
|
|
456
|
+
await harness.steer(p.text, opts);
|
|
457
|
+
},
|
|
458
|
+
async followUp(p) {
|
|
459
|
+
const opts = await toPiPromptOptions(p);
|
|
460
|
+
const harness = await harnessGate;
|
|
461
|
+
if (runSettled)
|
|
462
|
+
throw settledError();
|
|
463
|
+
await harness.followUp(p.text, opts);
|
|
464
|
+
},
|
|
465
|
+
async abort() {
|
|
466
|
+
const harness = await harnessGate;
|
|
467
|
+
if (runSettled)
|
|
468
|
+
throw settledError();
|
|
469
|
+
abortsInFlight++;
|
|
470
|
+
try {
|
|
471
|
+
await harness.abort();
|
|
472
|
+
abortSucceeded = true;
|
|
473
|
+
}
|
|
474
|
+
finally {
|
|
475
|
+
abortsInFlight--;
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
};
|
|
479
|
+
observe({ type: "run_started", timestamp: Date.now(), runId, data: {} }, controls);
|
|
258
480
|
try {
|
|
259
481
|
let harness;
|
|
260
482
|
try {
|
|
@@ -262,22 +484,58 @@ export function createPiAgentFromHarness(options) {
|
|
|
262
484
|
}
|
|
263
485
|
catch (error) {
|
|
264
486
|
// Setup failures (session open / auth / …) MUST surface as a failed event, never a throw.
|
|
265
|
-
|
|
266
|
-
|
|
487
|
+
harnessFailed(error); // a pending dispatch learns the run cannot take commands
|
|
488
|
+
const terminal = errorToTerminal(error);
|
|
489
|
+
outcome = { status: "failed", error: { message: terminal.details, retryable: terminal.retryable } };
|
|
490
|
+
runSettled = true; // commands can no longer take effect — reject stale controls from here on
|
|
491
|
+
yield terminal;
|
|
492
|
+
return; // → outer finally emits the settlement
|
|
493
|
+
}
|
|
494
|
+
harnessReady(harness);
|
|
495
|
+
// Arm the cancellation door (see invoke's wrapper): aborting the harness settles the run,
|
|
496
|
+
// releasing any await the generator is parked on so a queued return() can reach the finally.
|
|
497
|
+
onCancelReady(() => {
|
|
498
|
+
void harness.abort().catch(() => { });
|
|
499
|
+
});
|
|
500
|
+
// The consumer cancelled DURING the build (latched — the door above came too late to be
|
|
501
|
+
// knocked): never start the model call; settle as aborted and let the queued return()
|
|
502
|
+
// finish the generator. Same synchronous tick as the arming — a cancel from here on
|
|
503
|
+
// reaches the armed door instead.
|
|
504
|
+
if (wasCancelled()) {
|
|
505
|
+
outcome = { status: "aborted" };
|
|
506
|
+
runSettled = true;
|
|
507
|
+
try {
|
|
508
|
+
await harness.abort(); // teardown — fresh-harness discipline
|
|
509
|
+
}
|
|
510
|
+
catch (error) {
|
|
511
|
+
log.warn(`[fastagent] harness abort failed during cleanup: ${String(error)}`);
|
|
512
|
+
}
|
|
513
|
+
return; // → outer finally emits the settlement
|
|
267
514
|
}
|
|
268
515
|
const queue = new EventQueue();
|
|
269
516
|
const unsub = harness.subscribe((pe) => {
|
|
270
|
-
|
|
517
|
+
// Summarization retries also warn to server logs: the session `retry_scheduled` event only
|
|
518
|
+
// reaches attached observers, and an operator tailing logs must see the backoff too.
|
|
519
|
+
if (pe.type === "retry_scheduled") {
|
|
520
|
+
log.warn(`[fastagent] ${pe.operation} retry ${pe.attempt}/${pe.maxAttempts} in ${pe.delayMs}ms (session ${scope.session}): ${pe.errorMessage}`);
|
|
521
|
+
}
|
|
522
|
+
const rich = toSessionEvent(pe, runId);
|
|
523
|
+
if (!rich)
|
|
524
|
+
return;
|
|
525
|
+
observe(rich);
|
|
526
|
+
const event = projectAgentEvent(rich);
|
|
271
527
|
if (event)
|
|
272
528
|
queue.push(event);
|
|
273
529
|
});
|
|
274
530
|
let completed; // the assistant message of a cleanly completed turn
|
|
275
531
|
try {
|
|
276
|
-
//
|
|
277
|
-
// (turnContext / ToolContext.session). prompt() starts the async work synchronously here, so the
|
|
278
|
-
// store propagates to the tool calls awaited within it.
|
|
532
|
+
// Bind current cwd/session/activation capabilities for every FastAgent-defined tool.
|
|
279
533
|
const opts = await toPiPromptOptions(prompt);
|
|
280
|
-
const run = turnContext.run({
|
|
534
|
+
const run = turnContext.run({
|
|
535
|
+
cwd: options.cwd ?? process.cwd(),
|
|
536
|
+
sessionManager: toolSessionManager(scope.session, harness),
|
|
537
|
+
tools: toolActivation(harness),
|
|
538
|
+
}, () => harness.prompt(prompt.text, opts));
|
|
281
539
|
yield* queue.drainUntil(run);
|
|
282
540
|
let terminal;
|
|
283
541
|
try {
|
|
@@ -289,6 +547,28 @@ export function createPiAgentFromHarness(options) {
|
|
|
289
547
|
catch (error) {
|
|
290
548
|
terminal = errorToTerminal(error);
|
|
291
549
|
}
|
|
550
|
+
if ((abortSucceeded || abortsInFlight > 0) && terminal.type === "failed") {
|
|
551
|
+
terminal = { type: "failed", details: terminal.details, retryable: false, code: ABORTED_CODE };
|
|
552
|
+
}
|
|
553
|
+
if (terminal.type === "completed")
|
|
554
|
+
outcome = { status: "completed" };
|
|
555
|
+
else if (terminal.type === "failed") {
|
|
556
|
+
outcome =
|
|
557
|
+
terminal.code === ABORTED_CODE
|
|
558
|
+
? // Carry the detail: an independent real error that raced an accepted abort must stay
|
|
559
|
+
// diagnosable in the settlement (audit consumers read run_settled, not the invoke
|
|
560
|
+
// stream) — aborted classifies the run, the message preserves what actually stopped it.
|
|
561
|
+
{ status: "aborted", error: { message: terminal.details, retryable: false } }
|
|
562
|
+
: {
|
|
563
|
+
status: "failed",
|
|
564
|
+
error: { code: terminal.code, message: terminal.details, retryable: terminal.retryable },
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
// Commands become ineffective the moment the run resolved — NOT at the outer finally, which
|
|
568
|
+
// sits behind `yield terminal` (a consumer-paced suspension) and auto-compaction. Flipping
|
|
569
|
+
// here closes the silent-drop window for steer/follow_up dispatched in that gap; the
|
|
570
|
+
// outer-finally flip remains as the backstop for caller cancellation.
|
|
571
|
+
runSettled = true;
|
|
292
572
|
yield terminal;
|
|
293
573
|
}
|
|
294
574
|
finally {
|
|
@@ -326,6 +606,11 @@ export function createPiAgentFromHarness(options) {
|
|
|
326
606
|
}
|
|
327
607
|
}
|
|
328
608
|
finally {
|
|
609
|
+
// Exactly-one settlement, after ALL run work (incl. auto-compaction) and immediately before
|
|
610
|
+
// the lease releases — see the outcome note above. The stale-controls flag flips FIRST so a
|
|
611
|
+
// dispatch racing this settlement is rejected instead of silently accepted.
|
|
612
|
+
runSettled = true;
|
|
613
|
+
observe({ type: "run_settled", timestamp: Date.now(), runId, data: outcome ?? { status: "aborted" } });
|
|
329
614
|
release(); // after cleanup, so the next invoke for this session can enter
|
|
330
615
|
}
|
|
331
616
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `fastagent login`: authenticate a MODEL PROVIDER into the resolved auth file (project-level
|
|
3
|
-
* `<
|
|
3
|
+
* `<root>/.secrets/auth.json` by default, or `--auth-path`/`FASTAGENT_AUTH_PATH`) via the same
|
|
4
4
|
* {@link fastagentCredentialStore} the runtime uses (one writer, one lock/corruption semantics).
|
|
5
5
|
*
|
|
6
6
|
* Flow (pi-ai's unified `ProviderAuth` API): pick a method (OAuth or API key), then a provider that
|
|
@@ -33,6 +33,17 @@ export interface LoginIO {
|
|
|
33
33
|
openUrl(url: string): void;
|
|
34
34
|
}
|
|
35
35
|
export type LoginMethod = "oauth" | "api_key";
|
|
36
|
+
/** The user backed out of a prompt/menu — a decision, not a failure. Callers (the first-run picker,
|
|
37
|
+
* the login command) match on this to report neutrally instead of as a login "failure". */
|
|
38
|
+
export declare class LoginCancelled extends Error {
|
|
39
|
+
}
|
|
40
|
+
/** What `loginFlow` can offer a provider interactively: an OAuth flow, an API-key ENTRY prompt, or
|
|
41
|
+
* nothing ("none" — the key must come from the provider's env var). */
|
|
42
|
+
export type InteractiveLoginKind = LoginMethod | "none";
|
|
43
|
+
/** The provider's {@link InteractiveLoginKind}. OAuth wins when both exist (methodForProvider still
|
|
44
|
+
* asks at login time); the first-run picker annotates with this so the hint predicts what picking
|
|
45
|
+
* actually does — a browser login ("oauth"), a key prompt ("api_key"), or neither. */
|
|
46
|
+
export declare function interactiveLoginKind(p: Provider): InteractiveLoginKind;
|
|
36
47
|
export interface LoginResult {
|
|
37
48
|
provider: string;
|
|
38
49
|
method: LoginMethod;
|
package/dist/engines/pi/login.js
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { builtinProviders } from "@earendil-works/pi-ai/providers/all";
|
|
2
2
|
import { GLOBAL_AUTH_PATH, fastagentCredentialStore } from "./auth.js";
|
|
3
|
+
/** The user backed out of a prompt/menu — a decision, not a failure. Callers (the first-run picker,
|
|
4
|
+
* the login command) match on this to report neutrally instead of as a login "failure". */
|
|
5
|
+
export class LoginCancelled extends Error {
|
|
6
|
+
}
|
|
7
|
+
/** The provider's {@link InteractiveLoginKind}. OAuth wins when both exist (methodForProvider still
|
|
8
|
+
* asks at login time); the first-run picker annotates with this so the hint predicts what picking
|
|
9
|
+
* actually does — a browser login ("oauth"), a key prompt ("api_key"), or neither. */
|
|
10
|
+
export function interactiveLoginKind(p) {
|
|
11
|
+
if (p.auth.oauth)
|
|
12
|
+
return "oauth";
|
|
13
|
+
return p.auth.apiKey?.login ? "api_key" : "none";
|
|
14
|
+
}
|
|
3
15
|
/** Combine present abort signals into one (no-op when none/one). */
|
|
4
16
|
function anySignal(...signals) {
|
|
5
17
|
const present = signals.filter((s) => s !== undefined);
|
|
@@ -17,13 +29,13 @@ function authCallbacks(io, userSignal, doneSignal) {
|
|
|
17
29
|
if (p.type === "select") {
|
|
18
30
|
const v = await io.select(p.message, p.options.map((o) => ({ value: o.id, label: o.label, hint: o.description })));
|
|
19
31
|
if (v === undefined)
|
|
20
|
-
throw new
|
|
32
|
+
throw new LoginCancelled("cancelled");
|
|
21
33
|
return v;
|
|
22
34
|
}
|
|
23
35
|
const signal = anySignal(p.signal, userSignal, doneSignal);
|
|
24
36
|
const v = await io.prompt(p.message, { hidden: p.type === "secret", signal });
|
|
25
37
|
if (v === undefined)
|
|
26
|
-
throw new
|
|
38
|
+
throw new LoginCancelled("cancelled");
|
|
27
39
|
return v;
|
|
28
40
|
},
|
|
29
41
|
notify: (e) => {
|
|
@@ -53,7 +65,7 @@ async function selectMethod(io) {
|
|
|
53
65
|
{ value: "api_key", label: "Use an API key" },
|
|
54
66
|
]);
|
|
55
67
|
if (v !== "oauth" && v !== "api_key")
|
|
56
|
-
throw new
|
|
68
|
+
throw new LoginCancelled("no authentication method selected");
|
|
57
69
|
return v;
|
|
58
70
|
}
|
|
59
71
|
/** Given a provider arg, pick the method it supports (asking only when it offers both). */
|
|
@@ -80,7 +92,7 @@ async function selectProvider(io, providers, method, store) {
|
|
|
80
92
|
}));
|
|
81
93
|
const id = await io.select("Select a provider", options);
|
|
82
94
|
if (!id)
|
|
83
|
-
throw new
|
|
95
|
+
throw new LoginCancelled("no provider selected");
|
|
84
96
|
return id;
|
|
85
97
|
}
|
|
86
98
|
/**
|
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* it into the harness alongside the selected `model`; the two must come from the same collection so
|
|
5
5
|
* the model's provider auth is in scope.
|
|
6
6
|
*/
|
|
7
|
-
import { type Models, type Provider } from "@earendil-works/pi-ai";
|
|
7
|
+
import { type Api, type Model, type Models, type Provider } from "@earendil-works/pi-ai";
|
|
8
|
+
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
8
9
|
import { type FastagentAuthOptions } from "./auth.ts";
|
|
10
|
+
import { type InteractiveLoginKind } from "./login.ts";
|
|
9
11
|
export interface CreatePiModelsOptions extends FastagentAuthOptions {
|
|
10
|
-
/** Credentials file path. Defaults to the global `~/.fastagent/auth.json`; the directory opener passes
|
|
11
|
-
* the project-level `<
|
|
12
|
+
/** Credentials file path. Defaults to the global `~/.fastagent/.secrets/auth.json`; the directory opener passes
|
|
13
|
+
* the project-level `<root>/.secrets/auth.json`. */
|
|
12
14
|
authPath?: string;
|
|
13
15
|
/** Extra providers registered on top of the built-ins (same id overrides a built-in). */
|
|
14
16
|
providers?: Provider[];
|
|
@@ -16,22 +18,47 @@ export interface CreatePiModelsOptions extends FastagentAuthOptions {
|
|
|
16
18
|
/**
|
|
17
19
|
* A `Models` with every built-in pi provider, wired to fastagent's auth: stored credentials from the
|
|
18
20
|
* {@link CreatePiModelsOptions.authPath} file (via {@link fastagentCredentialStore}; the global
|
|
19
|
-
* `~/.fastagent/auth.json` unless the opener passes a project-level path), then ambient env vars. A
|
|
21
|
+
* `~/.fastagent/.secrets/auth.json` unless the opener passes a project-level path), then ambient env vars. A
|
|
20
22
|
* stored credential owns the provider; env is consulted only when nothing is stored (resolution order
|
|
21
23
|
* is upstream-owned).
|
|
22
24
|
*/
|
|
23
25
|
export declare function createPiModels(options?: CreatePiModelsOptions): Models;
|
|
24
26
|
/**
|
|
25
|
-
* The
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* to what the user can use and lets them choose (mirroring pi-coding-agent's select-then-persist).
|
|
27
|
+
* The `ModelRuntime`-shaped sibling of {@link createPiModels} — the SAME hub semantics (built-in
|
|
28
|
+
* providers + fastagent's credential store at `authPath`) in the type pi's session services require
|
|
29
|
+
* (`createAgentSessionServices({ modelRuntime })`). Builtins only (`modelsPath: null` — pi's
|
|
30
|
+
* machine-global models.json is definition-foreign) and no availability network, so the model
|
|
31
|
+
* surface equals serving's. No `providers` option: `ModelRuntime` registers providers by config
|
|
32
|
+
* record, not `Provider` instance — accepting the option and dropping it would be a silent no-op;
|
|
33
|
+
* add the mapping when a consumer actually needs it.
|
|
33
34
|
*/
|
|
34
|
-
export declare function
|
|
35
|
+
export declare function createPiModelRuntime(options?: FastagentAuthOptions & {
|
|
36
|
+
authPath?: string;
|
|
37
|
+
}): Promise<ModelRuntime>;
|
|
38
|
+
/** Per-provider auth status for the first-run model picker: usable now (with the source label), not
|
|
39
|
+
* configured, or configured-but-broken (expired token, refresh failure, corrupt store — kept as DATA
|
|
40
|
+
* so the picker can show it instead of silently dropping the provider). Non-ready states carry the
|
|
41
|
+
* provider's {@link InteractiveLoginKind}, so the picker's hint predicts what picking does — an
|
|
42
|
+
* OAuth login, an API-key prompt, or (env-key-only providers) neither. */
|
|
43
|
+
export type ProviderAuthStatus = {
|
|
44
|
+
state: "ready";
|
|
45
|
+
source?: string;
|
|
46
|
+
} | {
|
|
47
|
+
state: "unconfigured";
|
|
48
|
+
login: InteractiveLoginKind;
|
|
49
|
+
} | {
|
|
50
|
+
state: "broken";
|
|
51
|
+
message: string;
|
|
52
|
+
login: InteractiveLoginKind;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Probe every provider's auth once (auth is provider-scoped, so any of its models works as the probe)
|
|
56
|
+
* — the status map behind the first-run model picker (`fastagent dev`/`start`/`invoke` with no model
|
|
57
|
+
* set). The picker shows the FULL catalog annotated with these statuses, so "what fastagent supports"
|
|
58
|
+
* and "what is authenticated on this machine" stay distinguishable; a needs-login choice triggers an
|
|
59
|
+
* inline `loginFlow`. Providers with no models are omitted (nothing to pick).
|
|
60
|
+
*/
|
|
61
|
+
export declare function providerAuthStatuses(models: Models): Promise<Map<string, ProviderAuthStatus>>;
|
|
35
62
|
/**
|
|
36
63
|
* Which source currently satisfies auth for `spec` — a startup diagnostic. Returns the upstream
|
|
37
64
|
* `AuthResult.source` label: `"OAuth"` for a stored OAuth credential (e.g. a logged-in openai-codex),
|
|
@@ -39,3 +66,22 @@ export declare function configuredModelSpecs(models: Models): Promise<string[]>;
|
|
|
39
66
|
* undefined when unconfigured. Reporting-only; never throws.
|
|
40
67
|
*/
|
|
41
68
|
export declare function probeAuthSource(models: Models, spec: string): Promise<string | undefined>;
|
|
69
|
+
/** Verdict of {@link probeApiKey}: `rejected` is DEFINITIVE (the provider answered HTTP 401 — the key
|
|
70
|
+
* is wrong); everything else non-ok is `unknown` — a 403 can be a VALID key without model permission,
|
|
71
|
+
* a 429/5xx/network failure says nothing about the key — so callers must only destroy state on
|
|
72
|
+
* `rejected`. */
|
|
73
|
+
export type KeyProbe = {
|
|
74
|
+
state: "ok";
|
|
75
|
+
} | {
|
|
76
|
+
state: "rejected" | "unknown";
|
|
77
|
+
message: string;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Quick-fail probe for a just-stored API key: one minimal real request through the standard auth
|
|
81
|
+
* resolution path (the same path invokes take), so a mistyped key surfaces at login time, not at the
|
|
82
|
+
* first turn. `complete` reports provider errors as `stopReason: "error"` rather than throwing; the
|
|
83
|
+
* HTTP status arrives via `onResponse` — when a provider path never calls it (SDK transports), fall
|
|
84
|
+
* back to a conservative "401" match in the error text. Short timeout, no retries: feedback speed
|
|
85
|
+
* over transient-failure tolerance (a transient lands on `unknown`, which keeps the key).
|
|
86
|
+
*/
|
|
87
|
+
export declare function probeApiKey(models: Models, model: Model<Api>): Promise<KeyProbe>;
|