@agent-native/core 0.94.2 → 0.95.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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/docs/content/pr-visual-recap.mdx +20 -12
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/artifact-response.ts +154 -1
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +3 -0
- package/corpus/core/src/agent/engine/registry.ts +6 -1
- package/corpus/core/src/agent/engine/types.ts +2 -0
- package/corpus/core/src/cli/code-agent-executor.ts +61 -1
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
- package/corpus/core/src/cli/recap.ts +262 -25
- package/corpus/core/src/client/settings/UsageSection.tsx +26 -5
- package/corpus/core/src/index.ts +2 -0
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +118 -31
- package/corpus/core/src/integrations/a2a-continuations-store.ts +165 -7
- package/corpus/core/src/integrations/adapters/slack.ts +77 -7
- package/corpus/core/src/integrations/types.ts +30 -0
- package/corpus/core/src/integrations/webhook-handler.ts +54 -18
- package/corpus/core/src/scripts/call-agent.ts +1 -0
- package/corpus/core/src/server/request-context.ts +2 -0
- package/corpus/core/src/usage/store.ts +82 -20
- package/corpus/templates/plan/actions/record-recap-usage-cost.ts +49 -0
- package/corpus/templates/plan/actions/record-recap-usage.ts +15 -22
- package/dist/a2a/artifact-response.d.ts.map +1 -1
- package/dist/a2a/artifact-response.js +127 -1
- package/dist/a2a/artifact-response.js.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +3 -0
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/registry.d.ts +1 -1
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +2 -0
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/agent/engine/types.d.ts +2 -0
- package/dist/agent/engine/types.d.ts.map +1 -1
- package/dist/agent/engine/types.js.map +1 -1
- package/dist/cli/code-agent-executor.d.ts +2 -0
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js +52 -3
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/recap.d.ts +22 -2
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +199 -22
- package/dist/cli/recap.js.map +1 -1
- package/dist/client/settings/UsageSection.d.ts.map +1 -1
- package/dist/client/settings/UsageSection.js +12 -3
- package/dist/client/settings/UsageSection.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +76 -21
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/integrations/a2a-continuations-store.d.ts +4 -1
- package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
- package/dist/integrations/a2a-continuations-store.js +130 -6
- package/dist/integrations/a2a-continuations-store.js.map +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +56 -7
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/types.d.ts +25 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/integrations/webhook-handler.js +54 -16
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +1 -0
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/usage/store.d.ts +21 -0
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +54 -19
- package/dist/usage/store.js.map +1 -1
- package/docs/content/pr-visual-recap.mdx +20 -12
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.95.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 2308575: Add an OpenAI-compatible PR visual recap backend for DeepSeek, Kimi, and other compatible providers.
|
|
8
|
+
|
|
9
|
+
## 0.94.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 48d8471: Keep Slack agent task cards active through deferred cross-agent work, show durable progress, and deliver the true final result instead of treating interim artifacts as complete.
|
|
14
|
+
- 48d8471: Keep completed realtime voice turns in their originating chat, restore that chat safely when voice ends, and expose live audio activity to the voice dock.
|
|
15
|
+
- 48d8471: Accept canonical Content document links returned by successful read-only actions as A2A artifact proof while continuing to reject mismatched or unproven URLs.
|
|
16
|
+
|
|
3
17
|
## 0.94.2
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -7,7 +7,7 @@ description: "A GitHub Action that runs your repo's visual-recap skill on every
|
|
|
7
7
|
|
|
8
8
|
PR Visual Recap is a GitHub Action that turns every pull request into a **visual code review**. On each push, an LLM coding agent runs the latest bundled [`visual-recap`](/docs/template-plan) skill (or your repo's committed copy when `VISUAL_RECAP_SKILL_SOURCE=repo`) against the PR diff, publishes a structured recap plan to the hosted Plans app, shows an informational `Visual Recap` check while it runs, and upserts **one sticky PR comment** that links to the interactive plan with an **inline screenshot** embedded right in the comment.
|
|
9
9
|
|
|
10
|
-
This is not a deterministic diff renderer. The action invokes a real coding agent (Claude Code CLI by default,
|
|
10
|
+
This is not a deterministic diff renderer. The action invokes a real coding agent (Claude Code CLI by default, OpenAI Codex CLI, or Agent-Native Code with an OpenAI-compatible provider) that reads the change, decides what matters, and authors the recap by calling the Plans MCP tool `create-visual-recap` — the same tool the `/visual-recap` slash command uses. You get a high-altitude, schema/API/before-after view of the change instead of a wall of raw diff.
|
|
11
11
|
|
|
12
12
|
The recap is **informational and non-blocking**. It creates a check row so reviewers can see that generation is in progress, but it is not a required check, it never blocks the PR, and it never replaces reading the actual diff. The sticky comment is a review aid, not a sign-off.
|
|
13
13
|
|
|
@@ -36,7 +36,7 @@ On each PR push, the workflow:
|
|
|
36
36
|
<div class="diagram-arrow diagram-muted" aria-hidden="true">→</div>
|
|
37
37
|
<div class="diagram-node">
|
|
38
38
|
Coding agent<br /><small class="diagram-muted"
|
|
39
|
-
>
|
|
39
|
+
>configured backend reads diff</small
|
|
40
40
|
>
|
|
41
41
|
</div>
|
|
42
42
|
<div class="diagram-arrow diagram-muted" aria-hidden="true">→</div>
|
|
@@ -127,28 +127,32 @@ pins its committed `visual-recap` folder, set the repository variable
|
|
|
127
127
|
|
|
128
128
|
Choose which coding agent runs the skill with the `VISUAL_RECAP_AGENT` repository variable:
|
|
129
129
|
|
|
130
|
-
| `VISUAL_RECAP_AGENT` | Coding agent
|
|
131
|
-
| -------------------- |
|
|
132
|
-
| `claude` _(default)_ | Claude Code CLI
|
|
133
|
-
| `codex` | OpenAI Codex CLI
|
|
130
|
+
| `VISUAL_RECAP_AGENT` | Coding agent | Required API key | Required variables |
|
|
131
|
+
| -------------------- | --------------------------------------- | ---------------------- | --------------------------------------------- |
|
|
132
|
+
| `claude` _(default)_ | Claude Code CLI | `ANTHROPIC_API_KEY` | — |
|
|
133
|
+
| `codex` | OpenAI Codex CLI | `OPENAI_API_KEY` | — |
|
|
134
|
+
| `openai-compatible` | Agent-Native Code + compatible provider | `VISUAL_RECAP_API_KEY` | `VISUAL_RECAP_BASE_URL`, `VISUAL_RECAP_MODEL` |
|
|
134
135
|
|
|
135
136
|
If the variable is unset, the action uses `claude`.
|
|
136
137
|
|
|
137
138
|
## Model and reasoning
|
|
138
139
|
|
|
139
|
-
Beyond the backend,
|
|
140
|
+
Beyond the backend, these repository variables tune how the agent runs:
|
|
140
141
|
|
|
141
|
-
- **`VISUAL_RECAP_MODEL`** pins the model passed to the CLI (`--model`) — for example `gpt-5.6-sol` for Codex,
|
|
142
|
+
- **`VISUAL_RECAP_MODEL`** pins the model passed to the CLI (`--model`) — for example `gpt-5.6-sol` for Codex, a Claude model id, or `deepseek-chat` for DeepSeek. It is required for `openai-compatible`; Claude and Codex may leave it unset to use their CLI default.
|
|
142
143
|
- **`VISUAL_RECAP_REASONING`** sets the reasoning depth: `none`, `minimal`, `low`, `medium`, `high`, or `xhigh`. It applies to the Codex backend; Claude's reasoning is model-driven, so this variable is ignored there.
|
|
144
|
+
- **`VISUAL_RECAP_BASE_URL`** selects the HTTP(S) base URL for the `openai-compatible` backend. Use the provider's OpenAI-compatible endpoint, such as DeepSeek or Kimi, without embedding credentials in the URL.
|
|
143
145
|
- **`VISUAL_RECAP_SKILL_SOURCE`** controls prompt freshness: `auto`/unset uses the latest bundled skill guidance, while `repo` pins to the committed repo-local `visual-recap` skill folder.
|
|
144
146
|
|
|
145
147
|
For example, to run the recap on Codex with GPT-5.6 Sol at high reasoning, set the repository variables `VISUAL_RECAP_AGENT=codex`, `VISUAL_RECAP_MODEL=gpt-5.6-sol`, and `VISUAL_RECAP_REASONING=high`.
|
|
146
148
|
|
|
149
|
+
For DeepSeek, set `VISUAL_RECAP_AGENT=openai-compatible`, `VISUAL_RECAP_BASE_URL=https://api.deepseek.com/v1`, `VISUAL_RECAP_MODEL=deepseek-chat`, and the `VISUAL_RECAP_API_KEY` secret. Kimi works the same way with its OpenAI-compatible base URL and model id. Other OpenAI-compatible providers use the same three settings.
|
|
150
|
+
|
|
147
151
|
## Secrets and variables
|
|
148
152
|
|
|
149
153
|
Set these in your repository's **Settings → Secrets and variables → Actions**.
|
|
150
154
|
|
|
151
|
-
### Secrets
|
|
155
|
+
### Secrets for the default backend
|
|
152
156
|
|
|
153
157
|
| Secret | Purpose |
|
|
154
158
|
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -190,8 +194,10 @@ real token.
|
|
|
190
194
|
| Secret / variable | Default | When you need it |
|
|
191
195
|
| ------------------------ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
192
196
|
| `OPENAI_API_KEY` | — | Secret. Set together with `VISUAL_RECAP_AGENT=codex` to run the recap with Codex instead. |
|
|
193
|
-
| `
|
|
194
|
-
| `
|
|
197
|
+
| `VISUAL_RECAP_API_KEY` | — | Secret. Set with `VISUAL_RECAP_AGENT=openai-compatible` for DeepSeek, Kimi, or another OpenAI-compatible provider. |
|
|
198
|
+
| `VISUAL_RECAP_AGENT` | `claude` | Variable. Selects the coding-agent backend (`claude`, `codex`, or `openai-compatible`). |
|
|
199
|
+
| `VISUAL_RECAP_BASE_URL` | required for compatible backend | Variable. HTTP(S) base URL for the OpenAI-compatible backend; credentials are rejected. |
|
|
200
|
+
| `VISUAL_RECAP_MODEL` | required for compatible backend | Variable. Provider model id for OpenAI-compatible backends; optional for Claude/Codex, where unset uses the CLI default. |
|
|
195
201
|
| `VISUAL_RECAP_REASONING` | each model's default | Variable. Reasoning depth: `none`, `minimal`, `low`, `medium`, `high`, or `xhigh`. Applies to the Codex backend. |
|
|
196
202
|
| `RECAP_CLI_VERSION` | `latest` | Variable. Pins the `@agent-native/core` CLI version the workflow installs — e.g. `1.5.0`. See [Version pinning](#version-pinning-copy-variant). |
|
|
197
203
|
| `PLAN_RECAP_APP_URL` | `https://plan.agent-native.com` | Secret. Only when self-hosting the Plans app at a different origin. |
|
|
@@ -214,7 +220,7 @@ This also means you can merge the workflow file **before** the secrets exist: wi
|
|
|
214
220
|
|
|
215
221
|
If you want to generate recaps for fork PRs, a second workflow file is available: `.github/workflows/pr-visual-recap-fork.yml`. It uses `pull_request_target` (which runs with base-repo secrets) but never checks out or executes fork code. Trusted fork authors with GitHub author association `OWNER`, `MEMBER`, or `COLLABORATOR` run automatically. Outside fork PRs require an explicit **per-head maintainer opt-in** via a fresh `recap` label event before the recap agent runs.
|
|
216
222
|
|
|
217
|
-
To install it, copy the file from [BuilderIO/agent-native](https://github.com/BuilderIO/agent-native/blob/main/.github/workflows/pr-visual-recap-fork.yml) into your repo's `.github/workflows/` directory alongside the existing `pr-visual-recap.yml`. The same
|
|
223
|
+
To install it, copy the file from [BuilderIO/agent-native](https://github.com/BuilderIO/agent-native/blob/main/.github/workflows/pr-visual-recap-fork.yml) into your repo's `.github/workflows/` directory alongside the existing `pr-visual-recap.yml`. The same backend configuration applies, including `VISUAL_RECAP_API_KEY`, `VISUAL_RECAP_BASE_URL`, and `VISUAL_RECAP_MODEL` when using an OpenAI-compatible provider.
|
|
218
224
|
|
|
219
225
|
<Diagram id="doc-block-h3lne0" title="Fork PR consent gate" summary={"Fork PRs get no secrets by default; trusted authors run automatically, and outside contributors require a fresh maintainer recap label."}>
|
|
220
226
|
|
|
@@ -454,10 +460,12 @@ jobs:
|
|
|
454
460
|
PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }}
|
|
455
461
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
456
462
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
463
|
+
VISUAL_RECAP_API_KEY: ${{ secrets.VISUAL_RECAP_API_KEY }}
|
|
457
464
|
PLAN_RECAP_APP_URL: ${{ secrets.PLAN_RECAP_APP_URL }}
|
|
458
465
|
with:
|
|
459
466
|
agent: ${{ vars.VISUAL_RECAP_AGENT || 'claude' }}
|
|
460
467
|
model: ${{ vars.VISUAL_RECAP_MODEL || '' }}
|
|
468
|
+
base-url: ${{ vars.VISUAL_RECAP_BASE_URL || '' }}
|
|
461
469
|
reasoning: ${{ vars.VISUAL_RECAP_REASONING || '' }}
|
|
462
470
|
skill-source: ${{ vars.VISUAL_RECAP_SKILL_SOURCE || 'auto' }}
|
|
463
471
|
# cli-version: "latest" # pin to a specific @agent-native/core version
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.95.0",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -228,6 +228,132 @@ function contentDatabaseSubmissionArtifact(
|
|
|
228
228
|
};
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
function documentUrlForId(
|
|
232
|
+
parsed: Record<string, unknown>,
|
|
233
|
+
id: string,
|
|
234
|
+
additionalCandidates: Array<string | undefined> = [],
|
|
235
|
+
options: { requireContentOrigin?: boolean } = {},
|
|
236
|
+
): string | undefined {
|
|
237
|
+
const candidates = [
|
|
238
|
+
stringValue(parsed.url),
|
|
239
|
+
stringValue(parsed.urlPath),
|
|
240
|
+
stringValue(parsed.deepLink),
|
|
241
|
+
stringValue(parsed.pageUrl),
|
|
242
|
+
stringValue(parsed.documentUrl),
|
|
243
|
+
...additionalCandidates,
|
|
244
|
+
].filter((value): value is string => !!value);
|
|
245
|
+
|
|
246
|
+
return candidates.find((candidate) => {
|
|
247
|
+
if (!artifactUrlReferencesId(candidate, "document", id)) return false;
|
|
248
|
+
return !options.requireContentOrigin || isContentDocumentUrl(candidate);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function isContentDocumentUrl(rawUrl: string): boolean {
|
|
253
|
+
try {
|
|
254
|
+
return new URL(rawUrl).origin === "https://content.agent-native.com";
|
|
255
|
+
} catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function addDocumentReadArtifact(
|
|
261
|
+
documents: Map<string, CreatedDocumentArtifact>,
|
|
262
|
+
parsed: Record<string, unknown>,
|
|
263
|
+
options: {
|
|
264
|
+
allowWithoutUrl: boolean;
|
|
265
|
+
additionalUrlCandidates?: Array<string | undefined>;
|
|
266
|
+
requireContentOrigin?: boolean;
|
|
267
|
+
},
|
|
268
|
+
): void {
|
|
269
|
+
const id = stringValue(parsed.documentId) ?? stringValue(parsed.id);
|
|
270
|
+
if (!id) return;
|
|
271
|
+
|
|
272
|
+
const url = documentUrlForId(parsed, id, options.additionalUrlCandidates, {
|
|
273
|
+
requireContentOrigin: options.requireContentOrigin,
|
|
274
|
+
});
|
|
275
|
+
if (!url && !options.allowWithoutUrl) return;
|
|
276
|
+
|
|
277
|
+
documents.set(id, {
|
|
278
|
+
id,
|
|
279
|
+
title: stringValue(parsed.title) ?? stringValue(parsed.name),
|
|
280
|
+
url,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function addContentDatabaseReadArtifacts(
|
|
285
|
+
documents: Map<string, CreatedDocumentArtifact>,
|
|
286
|
+
parsed: Record<string, unknown>,
|
|
287
|
+
): void {
|
|
288
|
+
const resultUrls = [
|
|
289
|
+
stringValue(parsed.url),
|
|
290
|
+
stringValue(parsed.urlPath),
|
|
291
|
+
stringValue(parsed.deepLink),
|
|
292
|
+
];
|
|
293
|
+
const database = asRecord(parsed.database);
|
|
294
|
+
if (database) {
|
|
295
|
+
addDocumentReadArtifact(documents, database, {
|
|
296
|
+
allowWithoutUrl: true,
|
|
297
|
+
requireContentOrigin: true,
|
|
298
|
+
additionalUrlCandidates: resultUrls,
|
|
299
|
+
});
|
|
300
|
+
} else {
|
|
301
|
+
// Unavailable database reads still return a documentId, but they do not
|
|
302
|
+
// prove that the page exists and must not authorize an artifact URL.
|
|
303
|
+
if (parsed.available !== false) {
|
|
304
|
+
addDocumentReadArtifact(documents, parsed, {
|
|
305
|
+
allowWithoutUrl: true,
|
|
306
|
+
requireContentOrigin: true,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (!Array.isArray(parsed.items)) return;
|
|
312
|
+
for (const item of parsed.items) {
|
|
313
|
+
const itemRecord = asRecord(item);
|
|
314
|
+
const document = asRecord(itemRecord?.document);
|
|
315
|
+
if (!document) continue;
|
|
316
|
+
addDocumentReadArtifact(documents, document, {
|
|
317
|
+
allowWithoutUrl: true,
|
|
318
|
+
requireContentOrigin: true,
|
|
319
|
+
additionalUrlCandidates: [
|
|
320
|
+
stringValue(itemRecord?.url),
|
|
321
|
+
stringValue(itemRecord?.urlPath),
|
|
322
|
+
stringValue(itemRecord?.deepLink),
|
|
323
|
+
],
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function isGenericReadTool(tool: string): boolean {
|
|
329
|
+
return /^(?:find|get|list|query|read|search)-/i.test(tool);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function addGenericDocumentReadArtifact(
|
|
333
|
+
documents: Map<string, CreatedDocumentArtifact>,
|
|
334
|
+
parsed: Record<string, unknown>,
|
|
335
|
+
): void {
|
|
336
|
+
// Unknown read actions are accepted only when their result pairs a document
|
|
337
|
+
// ID with a canonical page URL containing that exact ID. An ID by itself is
|
|
338
|
+
// insufficient, preserving the fabrication guard for unrelated actions.
|
|
339
|
+
addDocumentReadArtifact(documents, parsed, {
|
|
340
|
+
allowWithoutUrl: false,
|
|
341
|
+
requireContentOrigin: true,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const document = asRecord(parsed.document);
|
|
345
|
+
if (!document) return;
|
|
346
|
+
addDocumentReadArtifact(documents, document, {
|
|
347
|
+
allowWithoutUrl: false,
|
|
348
|
+
requireContentOrigin: true,
|
|
349
|
+
additionalUrlCandidates: [
|
|
350
|
+
stringValue(parsed.url),
|
|
351
|
+
stringValue(parsed.urlPath),
|
|
352
|
+
stringValue(parsed.deepLink),
|
|
353
|
+
],
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
231
357
|
function addImageArtifact(
|
|
232
358
|
images: Map<string, CreatedImageArtifact>,
|
|
233
359
|
parsed: Record<string, unknown>,
|
|
@@ -387,7 +513,6 @@ function collectArtifacts(results: A2AToolResultSummary[]): {
|
|
|
387
513
|
|
|
388
514
|
if (
|
|
389
515
|
toolResult.tool === "create-document" ||
|
|
390
|
-
toolResult.tool === "get-document" ||
|
|
391
516
|
toolResult.tool === "update-document"
|
|
392
517
|
) {
|
|
393
518
|
const id = stringValue(parsed.id);
|
|
@@ -401,6 +526,34 @@ function collectArtifacts(results: A2AToolResultSummary[]): {
|
|
|
401
526
|
continue;
|
|
402
527
|
}
|
|
403
528
|
|
|
529
|
+
if (
|
|
530
|
+
toolResult.tool === "get-document" ||
|
|
531
|
+
toolResult.tool === "get-content-document"
|
|
532
|
+
) {
|
|
533
|
+
const document = asRecord(parsed.document);
|
|
534
|
+
addDocumentReadArtifact(documents, document ?? parsed, {
|
|
535
|
+
allowWithoutUrl: true,
|
|
536
|
+
requireContentOrigin: true,
|
|
537
|
+
additionalUrlCandidates: document
|
|
538
|
+
? [
|
|
539
|
+
stringValue(parsed.url),
|
|
540
|
+
stringValue(parsed.urlPath),
|
|
541
|
+
stringValue(parsed.deepLink),
|
|
542
|
+
]
|
|
543
|
+
: [],
|
|
544
|
+
});
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (toolResult.tool === "get-content-database") {
|
|
549
|
+
addContentDatabaseReadArtifacts(documents, parsed);
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (isGenericReadTool(toolResult.tool)) {
|
|
554
|
+
addGenericDocumentReadArtifact(documents, parsed);
|
|
555
|
+
}
|
|
556
|
+
|
|
404
557
|
if (
|
|
405
558
|
toolResult.tool === "create-deck" ||
|
|
406
559
|
toolResult.tool === "duplicate-deck"
|
|
@@ -205,6 +205,7 @@ class AISDKEngine implements AgentEngine {
|
|
|
205
205
|
readonly label: string;
|
|
206
206
|
readonly defaultModel: string;
|
|
207
207
|
readonly supportedModels: readonly string[];
|
|
208
|
+
readonly preserveCustomModels: boolean;
|
|
208
209
|
readonly capabilities: EngineCapabilities;
|
|
209
210
|
|
|
210
211
|
private readonly provider: AISDKProvider;
|
|
@@ -219,6 +220,8 @@ class AISDKEngine implements AgentEngine {
|
|
|
219
220
|
this.label = `${capitalize(provider)} (AI SDK)`;
|
|
220
221
|
this.defaultModel = config.model ?? PROVIDER_DEFAULT_MODELS[provider];
|
|
221
222
|
this.supportedModels = PROVIDER_SUPPORTED_MODELS[provider];
|
|
223
|
+
this.preserveCustomModels =
|
|
224
|
+
provider === "openai" && Boolean(config.baseUrl);
|
|
222
225
|
this.capabilities = PROVIDER_CAPABILITIES[provider];
|
|
223
226
|
this.apiKey =
|
|
224
227
|
config.apiKey ??
|
|
@@ -180,12 +180,17 @@ function findLatestSupportedVersionMatch(
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
export function normalizeModelForEngine(
|
|
183
|
-
engine: Pick<
|
|
183
|
+
engine: Pick<
|
|
184
|
+
AgentEngine,
|
|
185
|
+
"name" | "defaultModel" | "supportedModels" | "preserveCustomModels"
|
|
186
|
+
>,
|
|
184
187
|
model: string | null | undefined,
|
|
185
188
|
): string {
|
|
186
189
|
const candidate = typeof model === "string" ? model.trim() : "";
|
|
187
190
|
if (!candidate) return engine.defaultModel;
|
|
188
191
|
|
|
192
|
+
if (engine.preserveCustomModels) return candidate;
|
|
193
|
+
|
|
189
194
|
if (engine.supportedModels.length === 0) return candidate;
|
|
190
195
|
|
|
191
196
|
if (candidate === "auto" || engine.supportedModels.includes(candidate)) {
|
|
@@ -297,6 +297,8 @@ export interface AgentEngine {
|
|
|
297
297
|
readonly defaultModel: string;
|
|
298
298
|
/** Models this engine supports */
|
|
299
299
|
readonly supportedModels: readonly string[];
|
|
300
|
+
/** Whether the configured endpoint accepts provider-defined model ids. */
|
|
301
|
+
readonly preserveCustomModels?: boolean;
|
|
300
302
|
/** Capability flags used to gate provider-specific features */
|
|
301
303
|
readonly capabilities: EngineCapabilities;
|
|
302
304
|
|
|
@@ -101,6 +101,10 @@ const DEFAULT_COMMAND_TIMEOUT_MS = 120_000;
|
|
|
101
101
|
const MAX_TOOL_OUTPUT_CHARS = 50_000;
|
|
102
102
|
const MAX_FILE_READ_CHARS = 120_000;
|
|
103
103
|
const CODEX_CLI_ENGINE_NAME = "codex-cli";
|
|
104
|
+
const RECAP_SOURCE_TOOL_PROFILE = "recap-source";
|
|
105
|
+
const RECAP_SOURCE_OUTPUT_FILE = "recap-source.json";
|
|
106
|
+
|
|
107
|
+
type CodeAgentToolProfile = typeof RECAP_SOURCE_TOOL_PROFILE;
|
|
104
108
|
|
|
105
109
|
/**
|
|
106
110
|
* Number of most-recent transcript events reconstructed as native
|
|
@@ -237,6 +241,9 @@ export async function executeCodeAgentRun(
|
|
|
237
241
|
options.reasoningEffort ?? metadataReasoningEffort(existing);
|
|
238
242
|
const cwd = existing.cwd || process.cwd();
|
|
239
243
|
const permissionMode = existing.permissionMode ?? "full-auto";
|
|
244
|
+
const toolProfile = resolveCodeAgentToolProfile(
|
|
245
|
+
process.env.AGENT_NATIVE_CODE_TOOL_PROFILE,
|
|
246
|
+
);
|
|
240
247
|
|
|
241
248
|
// Holds structured metadata emitted by the coding tools side-channel.
|
|
242
249
|
// Keyed by tool name; consumed when the matching tool_start / tool_done fires.
|
|
@@ -254,8 +261,11 @@ export async function executeCodeAgentRun(
|
|
|
254
261
|
// Stream incremental bash output to stdout for the terminal smoother
|
|
255
262
|
options.stdout?.write(chunk);
|
|
256
263
|
},
|
|
264
|
+
toolProfile,
|
|
257
265
|
);
|
|
258
|
-
const mcpManager =
|
|
266
|
+
const mcpManager = toolProfile
|
|
267
|
+
? null
|
|
268
|
+
: await startCodeAgentMcpManager(existing.id);
|
|
259
269
|
if (mcpManager) {
|
|
260
270
|
Object.assign(
|
|
261
271
|
actions,
|
|
@@ -392,6 +402,7 @@ export async function executeCodeAgentRun(
|
|
|
392
402
|
}),
|
|
393
403
|
);
|
|
394
404
|
loopUsage = usageResult ?? null;
|
|
405
|
+
writeCodeAgentUsageSnapshot(cwd, loopUsage);
|
|
395
406
|
// Persist cumulative token totals from this turn into the run record so
|
|
396
407
|
// the UI can display per-run usage statistics.
|
|
397
408
|
if (loopUsage) {
|
|
@@ -1663,6 +1674,7 @@ function createLocalCodeAgentActions(
|
|
|
1663
1674
|
meta: StructuredToolMetadata,
|
|
1664
1675
|
) => void,
|
|
1665
1676
|
onBashOutputChunk?: (chunk: string) => void,
|
|
1677
|
+
toolProfile?: CodeAgentToolProfile,
|
|
1666
1678
|
): Record<string, ActionEntry> {
|
|
1667
1679
|
const actions = createCodingToolRegistry({
|
|
1668
1680
|
cwd,
|
|
@@ -1701,6 +1713,24 @@ function createLocalCodeAgentActions(
|
|
|
1701
1713
|
return null;
|
|
1702
1714
|
},
|
|
1703
1715
|
});
|
|
1716
|
+
if (toolProfile === RECAP_SOURCE_TOOL_PROFILE) {
|
|
1717
|
+
const allowedOutput = path.resolve(cwd, RECAP_SOURCE_OUTPUT_FILE);
|
|
1718
|
+
const writeAction = actions.write;
|
|
1719
|
+
return {
|
|
1720
|
+
read: actions.read,
|
|
1721
|
+
write: {
|
|
1722
|
+
...writeAction,
|
|
1723
|
+
run: async (args, context) => {
|
|
1724
|
+
const requestedPath =
|
|
1725
|
+
args && typeof args.path === "string" ? args.path : "";
|
|
1726
|
+
if (path.resolve(cwd, requestedPath) !== allowedOutput) {
|
|
1727
|
+
return `Error: only ${RECAP_SOURCE_OUTPUT_FILE} may be written in the recap-source tool profile.`;
|
|
1728
|
+
}
|
|
1729
|
+
return writeAction.run(args, context);
|
|
1730
|
+
},
|
|
1731
|
+
},
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1704
1734
|
if (permissionMode === "read-only") {
|
|
1705
1735
|
return {
|
|
1706
1736
|
bash: actions.bash,
|
|
@@ -1710,6 +1740,15 @@ function createLocalCodeAgentActions(
|
|
|
1710
1740
|
return actions;
|
|
1711
1741
|
}
|
|
1712
1742
|
|
|
1743
|
+
function resolveCodeAgentToolProfile(
|
|
1744
|
+
value: string | undefined,
|
|
1745
|
+
): CodeAgentToolProfile | undefined {
|
|
1746
|
+
const normalized = value?.trim();
|
|
1747
|
+
if (!normalized) return undefined;
|
|
1748
|
+
if (normalized === RECAP_SOURCE_TOOL_PROFILE) return normalized;
|
|
1749
|
+
throw new Error(`Unsupported Agent-Native Code tool profile: ${normalized}`);
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1713
1752
|
export type CodeAgentCommandPermission =
|
|
1714
1753
|
| { kind: "read" }
|
|
1715
1754
|
| { kind: "write" }
|
|
@@ -1863,6 +1902,27 @@ function getPendingApproval(runId: string): PendingCodeAgentApproval | null {
|
|
|
1863
1902
|
};
|
|
1864
1903
|
}
|
|
1865
1904
|
|
|
1905
|
+
export function writeCodeAgentUsageSnapshot(
|
|
1906
|
+
cwd: string,
|
|
1907
|
+
usage: AgentLoopUsage | null,
|
|
1908
|
+
): void {
|
|
1909
|
+
const configuredPath = process.env.AGENT_NATIVE_CODE_USAGE_FILE?.trim();
|
|
1910
|
+
if (!configuredPath || !usage) return;
|
|
1911
|
+
|
|
1912
|
+
const outputPath = path.isAbsolute(configuredPath)
|
|
1913
|
+
? configuredPath
|
|
1914
|
+
: path.resolve(cwd, configuredPath);
|
|
1915
|
+
try {
|
|
1916
|
+
fs.writeFileSync(outputPath, `${JSON.stringify(usage)}\n`, {
|
|
1917
|
+
encoding: "utf8",
|
|
1918
|
+
mode: 0o600,
|
|
1919
|
+
});
|
|
1920
|
+
} catch {
|
|
1921
|
+
// Usage reporting is best-effort and must not turn a completed agent run
|
|
1922
|
+
// into a failed recap when the optional sidecar cannot be written.
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1866
1926
|
// --------------- Token usage accumulator ---------------
|
|
1867
1927
|
|
|
1868
1928
|
interface StoredTokenUsage {
|