shortcutxl 0.3.69 → 0.3.70
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/BINARY-INVENTORY.json +821 -813
- package/CHANGELOG.md +5 -0
- package/agent-docs/docs/providers.md +1 -1
- package/agent-docs/docs/rpc.md +1 -1
- package/agent-docs/docs/session.md +1 -1
- package/agent-docs/examples/extensions/custom-provider-gitlab-duo/index.ts +6 -5
- package/agent-docs/examples/extensions/custom-provider-gitlab-duo/test.ts +1 -1
- package/agent-docs/examples/extensions/summarize.ts +6 -3
- package/dist/ai/models.d.ts +1 -1
- package/dist/ai/models.js +4 -2
- package/dist/ai/providers/anthropic.js +34 -2
- package/dist/ai/providers/openai-responses-shared.d.ts +1 -1
- package/dist/ai/providers/openai-responses-shared.js +68 -5
- package/dist/ai/providers/openai-responses.d.ts +2 -2
- package/dist/ai/providers/openai-responses.js +6 -7
- package/dist/ai/providers/provider-error-normalization.js +14 -4
- package/dist/app/agent-session.d.ts +4 -1
- package/dist/app/agent-session.js +15 -8
- package/dist/app/providers/model-resolver.js +11 -7
- package/dist/app/providers/register-shortcut-provider.js +22 -6
- package/dist/app/providers/shortcut-llm-proxy-client.js +2 -3
- package/dist/app/session/compaction-observability.d.ts +17 -0
- package/dist/app/session/compaction-observability.js +83 -0
- package/dist/app/session/session-models.js +4 -5
- package/dist/app/subagents/defaults.d.ts +2 -2
- package/dist/app/subagents/defaults.js +6 -3
- package/dist/app/tools/read.js +14 -0
- package/dist/app/tools/task/task.js +2 -2
- package/dist/cli/args.js +3 -2
- package/dist/cli/file-processor.d.ts +2 -2
- package/dist/cli/file-processor.js +8 -27
- package/dist/cli.js +643 -193
- package/dist/contracts/agent-failure.d.ts +1 -0
- package/dist/contracts/agent-failure.js +1 -0
- package/dist/contracts/agent-submit.d.ts +2 -2
- package/dist/contracts/model-call.d.ts +1 -0
- package/dist/contracts/model-stream.d.ts +7 -5
- package/dist/contracts/model-stream.js +52 -0
- package/dist/contracts/wire.d.ts +3 -0
- package/dist/core/prompts/agent-guidelines.d.ts +1 -1
- package/dist/core/prompts/agent-guidelines.js +1 -1
- package/dist/core/run-failure.js +14 -0
- package/dist/core/session/session-compaction.js +1 -1
- package/dist/embedded-agent/ai-invoke.d.ts +1 -0
- package/dist/embedded-agent/ai-invoke.js +2 -0
- package/dist/embedded-agent/anthropic-messages-transport.js +121 -23
- package/dist/embedded-agent/compose.js +2 -1
- package/dist/embedded-agent/google-vertex-transport.js +21 -9
- package/dist/embedded-agent/host-tools/invoke-llm/contract.d.ts +1 -0
- package/dist/embedded-agent/host-tools/task/agent-definition.d.ts +2 -1
- package/dist/embedded-agent/host-tools/task/agent-definition.js +2 -0
- package/dist/embedded-agent/host-tools/task/agents/clone.js +2 -3
- package/dist/embedded-agent/host-tools/task/agents/document-reader.js +2 -3
- package/dist/embedded-agent/host-tools/task/agents/simulation.js +2 -3
- package/dist/embedded-agent/host-tools/task/agents/verification.js +2 -4
- package/dist/embedded-agent/host-tools/task/agents/workbook-reader.js +2 -3
- package/dist/embedded-agent/host-tools/task/contract.d.ts +1 -1
- package/dist/embedded-agent/host-tools/task/contract.js +5 -4
- package/dist/embedded-agent/host-tools/task/index.d.ts +1 -1
- package/dist/embedded-agent/host-tools/task/index.js +1 -0
- package/dist/embedded-agent/model-registry.d.ts +1 -2
- package/dist/embedded-agent/model-registry.js +3 -6
- package/dist/embedded-agent/openai-completions-transport.js +24 -12
- package/dist/embedded-agent/openai-responses-transport.js +25 -12
- package/dist/embedded-agent/provider-fast-mode.d.ts +30 -0
- package/dist/embedded-agent/provider-fast-mode.js +120 -0
- package/dist/embedded-agent/stream-errors.js +4 -3
- package/dist/embedded-agent/suggestions.d.ts +1 -0
- package/dist/embedded-agent/suggestions.js +1 -0
- package/dist/fast-mode.d.ts +1 -1
- package/dist/fast-mode.js +8 -7
- package/dist/main.js +5 -0
- package/dist/model-display.js +2 -1
- package/dist/model-ids.d.ts +4 -4
- package/dist/model-ids.js +5 -5
- package/dist/shared/files/file-content.d.ts +7 -0
- package/dist/shared/files/file-content.js +60 -0
- package/dist/shell/theme/theme-schema.json +1 -1
- package/dist/shortcut-model-catalog.d.ts +1 -1
- package/dist/shortcut-model-catalog.js +5 -15
- package/package.json +52 -53
- package/user-docs/dist/shortcutxl-docs.pdf +0 -0
- package/xll/ShortcutXL.xll +0 -0
- package/xll/python/Lib/site-packages/httpx-0.28.1.dist-info/RECORD +1 -1
- package/xll/python/Lib/site-packages/idna-3.18.dist-info/RECORD +1 -1
- package/xll/python/Lib/site-packages/pip-26.1.2.dist-info/RECORD +3 -3
- package/xll/python/Lib/site-packages/pywin32-311.dist-info/RECORD +2 -2
- package/xll/python/Scripts/httpx.exe +0 -0
- package/xll/python/Scripts/idna.exe +0 -0
- package/xll/python/Scripts/pip.exe +0 -0
- package/xll/python/Scripts/pip3.13.exe +0 -0
- package/xll/python/Scripts/pip3.exe +0 -0
- package/xll/python/Scripts/pywin32_postinstall.exe +0 -0
- package/xll/python/Scripts/pywin32_testall.exe +0 -0
- package/xll/python/vcruntime140.dll +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.70]
|
|
4
|
+
|
|
5
|
+
- **GPT-5.6 Sol default** - ShortcutXL now defaults CLI and embedded sessions to GPT-5.6 Sol and shows clearer compact model labels.
|
|
6
|
+
- **Path-based attachments** - Fixed CLI file attachments that reference files by path.
|
|
7
|
+
|
|
3
8
|
## [0.3.69]
|
|
4
9
|
|
|
5
10
|
- **CLI screenshot capture** - ShortcutXL can now capture screenshots from CLI sessions, giving agents visual context when working with Excel or the desktop.
|
|
@@ -101,7 +101,7 @@ export AZURE_OPENAI_RESOURCE_NAME=your-resource
|
|
|
101
101
|
|
|
102
102
|
# Optional
|
|
103
103
|
export AZURE_OPENAI_API_VERSION=2024-02-01
|
|
104
|
-
export AZURE_OPENAI_DEPLOYMENT_NAME_MAP
|
|
104
|
+
export AZURE_OPENAI_DEPLOYMENT_NAME_MAP=<openai-model-id>=my-openai-deployment
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
### Amazon Bedrock
|
package/agent-docs/docs/rpc.md
CHANGED
|
@@ -302,7 +302,7 @@ Set the reasoning/thinking level for models that support it.
|
|
|
302
302
|
|
|
303
303
|
Levels: `"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`
|
|
304
304
|
|
|
305
|
-
Note: `"xhigh"` is only supported by the
|
|
305
|
+
Note: `"xhigh"` is only supported by the OpenAI reasoning and Claude Opus 4.8 model families.
|
|
306
306
|
|
|
307
307
|
Response:
|
|
308
308
|
|
|
@@ -212,7 +212,7 @@ A message in the conversation. The `message` field contains an `AgentMessage`.
|
|
|
212
212
|
Emitted when the user switches models mid-session.
|
|
213
213
|
|
|
214
214
|
```json
|
|
215
|
-
{"type":"model_change","id":"d4e5f6g7","parentId":"c3d4e5f6","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"
|
|
215
|
+
{"type":"model_change","id":"d4e5f6g7","parentId":"c3d4e5f6","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"openai-reasoning-model"}
|
|
216
216
|
```
|
|
217
217
|
|
|
218
218
|
### ThinkingLevelChangeEntry
|
|
@@ -30,6 +30,7 @@ const GITLAB_COM_URL = 'https://gitlab.com';
|
|
|
30
30
|
const AI_GATEWAY_URL = 'https://cloud.gitlab.com';
|
|
31
31
|
const ANTHROPIC_PROXY_URL = `${AI_GATEWAY_URL}/ai/v1/proxy/anthropic/`;
|
|
32
32
|
const OPENAI_PROXY_URL = `${AI_GATEWAY_URL}/ai/v1/proxy/openai/v1`;
|
|
33
|
+
const OPENAI_REASONING_MODEL_ID = 'gpt-5.6-sol';
|
|
33
34
|
|
|
34
35
|
const BUNDLED_CLIENT_ID = 'da4edff2e6ebd2bc3208611e2768bc1c1dd7be791dc5ff26ca34ca9ee44f7d4b';
|
|
35
36
|
const OAUTH_SCOPES = ['.shortcut'];
|
|
@@ -91,15 +92,15 @@ export const MODELS: GitLabModel[] = [
|
|
|
91
92
|
},
|
|
92
93
|
// OpenAI (all use Responses API)
|
|
93
94
|
{
|
|
94
|
-
id:
|
|
95
|
-
name: 'GPT 5.
|
|
95
|
+
id: OPENAI_REASONING_MODEL_ID,
|
|
96
|
+
name: 'GPT 5.6 Sol',
|
|
96
97
|
backend: 'openai',
|
|
97
98
|
baseUrl: OPENAI_PROXY_URL,
|
|
98
99
|
reasoning: true,
|
|
99
100
|
input: ['text', 'image'],
|
|
100
|
-
cost: { input:
|
|
101
|
-
contextWindow:
|
|
102
|
-
maxTokens:
|
|
101
|
+
cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
102
|
+
contextWindow: 1000000,
|
|
103
|
+
maxTokens: 128000
|
|
103
104
|
}
|
|
104
105
|
];
|
|
105
106
|
|
|
@@ -3,6 +3,9 @@ import type { ExtensionAPI, ExtensionCommandContext } from 'shortcutxl/app';
|
|
|
3
3
|
import { DynamicBorder, getMarkdownTheme } from 'shortcutxl/shell';
|
|
4
4
|
import { Container, Markdown, matchesKey, Text } from 'shortcutxl/tui';
|
|
5
5
|
|
|
6
|
+
const OPENAI_SUMMARY_MODEL_ID = 'gpt-5.6-sol';
|
|
7
|
+
const OPENAI_SUMMARY_MODEL_REF = `openai/${OPENAI_SUMMARY_MODEL_ID}`;
|
|
8
|
+
|
|
6
9
|
type ContentBlock = {
|
|
7
10
|
type?: string;
|
|
8
11
|
text?: string;
|
|
@@ -160,14 +163,14 @@ export default function (shortcut: ExtensionAPI) {
|
|
|
160
163
|
ctx.ui.notify('Preparing summary...', 'info');
|
|
161
164
|
}
|
|
162
165
|
|
|
163
|
-
const model = getModel('openai',
|
|
166
|
+
const model = getModel('openai', OPENAI_SUMMARY_MODEL_ID);
|
|
164
167
|
if (!model && ctx.hasUI) {
|
|
165
|
-
ctx.ui.notify(
|
|
168
|
+
ctx.ui.notify(`Model ${OPENAI_SUMMARY_MODEL_REF} not found`, 'warning');
|
|
166
169
|
}
|
|
167
170
|
|
|
168
171
|
const apiKey = model ? await ctx.modelRegistry.getApiKey(model) : undefined;
|
|
169
172
|
if (!apiKey && ctx.hasUI) {
|
|
170
|
-
ctx.ui.notify(
|
|
173
|
+
ctx.ui.notify(`No API key for ${OPENAI_SUMMARY_MODEL_REF}`, 'warning');
|
|
171
174
|
}
|
|
172
175
|
|
|
173
176
|
if (!model || !apiKey) {
|
package/dist/ai/models.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare function calculateCost<TApi extends Api>(model: Model<TApi>, usag
|
|
|
11
11
|
* Check if a model supports xhigh thinking level.
|
|
12
12
|
*
|
|
13
13
|
* Supported today:
|
|
14
|
-
* -
|
|
14
|
+
* - Shortcut's current and legacy OpenAI reasoning models
|
|
15
15
|
* - Anthropic Messages API Opus 4.8 models (xhigh maps to adaptive effort "max")
|
|
16
16
|
*/
|
|
17
17
|
export declare function supportsXhigh<TApi extends Api>(model: Model<TApi>): boolean;
|
package/dist/ai/models.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SHORTCUT_OPENAI_MODEL_ID } from '../model-ids.js';
|
|
1
2
|
import { MODELS } from './models.generated.js';
|
|
2
3
|
const modelRegistry = new Map();
|
|
3
4
|
// Initialize registry from MODELS on module load
|
|
@@ -35,11 +36,12 @@ export function calculateCost(model, usage) {
|
|
|
35
36
|
* Check if a model supports xhigh thinking level.
|
|
36
37
|
*
|
|
37
38
|
* Supported today:
|
|
38
|
-
* -
|
|
39
|
+
* - Shortcut's current and legacy OpenAI reasoning models
|
|
39
40
|
* - Anthropic Messages API Opus 4.8 models (xhigh maps to adaptive effort "max")
|
|
40
41
|
*/
|
|
41
42
|
export function supportsXhigh(model) {
|
|
42
|
-
|
|
43
|
+
const modelId = model.id.startsWith('openai/') ? model.id.slice('openai/'.length) : model.id;
|
|
44
|
+
if (modelId.includes(SHORTCUT_OPENAI_MODEL_ID) || modelId.includes('gpt-5.5')) {
|
|
43
45
|
return true;
|
|
44
46
|
}
|
|
45
47
|
if (model.api === 'anthropic-messages') {
|
|
@@ -85,11 +85,43 @@ function convertContentBlocks(content) {
|
|
|
85
85
|
}
|
|
86
86
|
function mergeHeaders(...headerSources) {
|
|
87
87
|
const merged = {};
|
|
88
|
+
const betaHeaderName = 'anthropic-beta';
|
|
89
|
+
const betaTokens = [];
|
|
90
|
+
const seenBetaTokens = new Set();
|
|
91
|
+
const addBetaTokens = (value) => {
|
|
92
|
+
for (const token of value.split(',')) {
|
|
93
|
+
const trimmed = token.trim();
|
|
94
|
+
if (!trimmed)
|
|
95
|
+
continue;
|
|
96
|
+
const key = trimmed.toLowerCase();
|
|
97
|
+
if (seenBetaTokens.has(key))
|
|
98
|
+
continue;
|
|
99
|
+
seenBetaTokens.add(key);
|
|
100
|
+
betaTokens.push(trimmed);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
88
103
|
for (const headers of headerSources) {
|
|
89
104
|
if (headers) {
|
|
90
|
-
Object.
|
|
105
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
106
|
+
if (key.toLowerCase() === betaHeaderName) {
|
|
107
|
+
delete merged[key];
|
|
108
|
+
if (typeof value === 'string') {
|
|
109
|
+
addBetaTokens(value);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
betaTokens.length = 0;
|
|
113
|
+
seenBetaTokens.clear();
|
|
114
|
+
merged[betaHeaderName] = value;
|
|
115
|
+
}
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
merged[key] = value;
|
|
119
|
+
}
|
|
91
120
|
}
|
|
92
121
|
}
|
|
122
|
+
if (betaTokens.length > 0) {
|
|
123
|
+
merged[betaHeaderName] = betaTokens.join(',');
|
|
124
|
+
}
|
|
93
125
|
return merged;
|
|
94
126
|
}
|
|
95
127
|
export const streamAnthropic = (model, context, options) => {
|
|
@@ -444,7 +476,7 @@ function buildParams(model, context, options) {
|
|
|
444
476
|
if (options?.thinkingEnabled && model.reasoning) {
|
|
445
477
|
if (supportsAdaptiveThinking(model.id)) {
|
|
446
478
|
// Adaptive thinking: Claude decides when and how much to think
|
|
447
|
-
params.thinking = { type: 'adaptive' };
|
|
479
|
+
params.thinking = { type: 'adaptive', display: 'summarized' };
|
|
448
480
|
if (options.effort) {
|
|
449
481
|
params.output_config = { effort: options.effort };
|
|
450
482
|
}
|
|
@@ -3,7 +3,7 @@ import type { Api, AssistantMessage, Context, Model, Tool, Usage } from '../type
|
|
|
3
3
|
import type { AssistantMessageEventStream } from '../utils/event-stream.js';
|
|
4
4
|
export interface OpenAIResponsesStreamOptions {
|
|
5
5
|
serviceTier?: ResponseCreateParamsStreaming['service_tier'];
|
|
6
|
-
applyServiceTierPricing?: (usage: Usage, serviceTier: ResponseCreateParamsStreaming['service_tier'] | undefined
|
|
6
|
+
applyServiceTierPricing?: (usage: Usage, serviceTier: ResponseCreateParamsStreaming['service_tier'] | undefined) => void;
|
|
7
7
|
}
|
|
8
8
|
export interface ConvertResponsesMessagesOptions {
|
|
9
9
|
includeSystemPrompt?: boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { safeModelStreamErrorMessage } from '../../contracts/model-stream.js';
|
|
1
2
|
import { calculateCost } from '../models.js';
|
|
2
3
|
import { parseStreamingJson } from '../utils/json-parse.js';
|
|
3
4
|
import { sanitizeSurrogates } from '../utils/sanitize-unicode.js';
|
|
@@ -5,6 +6,46 @@ import { transformMessages } from './transform-messages.js';
|
|
|
5
6
|
// =============================================================================
|
|
6
7
|
// Utilities
|
|
7
8
|
// =============================================================================
|
|
9
|
+
class OpenAIResponsesStreamError extends Error {
|
|
10
|
+
details;
|
|
11
|
+
constructor(details) {
|
|
12
|
+
super(safeModelStreamErrorMessage(details));
|
|
13
|
+
this.name = 'OpenAIResponsesStreamError';
|
|
14
|
+
this.details = details;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function isOpenAIResponsesInvalidRequestCode(code) {
|
|
18
|
+
return (code?.startsWith('invalid_') === true ||
|
|
19
|
+
code === 'empty_image_file' ||
|
|
20
|
+
code === 'failed_to_download_image' ||
|
|
21
|
+
code === 'image_content_policy_violation' ||
|
|
22
|
+
code === 'image_file_not_found' ||
|
|
23
|
+
code === 'image_parse_error' ||
|
|
24
|
+
code === 'image_too_large' ||
|
|
25
|
+
code === 'image_too_small' ||
|
|
26
|
+
code === 'unsupported_image_media_type');
|
|
27
|
+
}
|
|
28
|
+
function openAIResponsesStreamErrorDetails(error) {
|
|
29
|
+
const code = error?.code?.trim() || undefined;
|
|
30
|
+
if (code === 'rate_limit_exceeded') {
|
|
31
|
+
return { code, providerErrorType: 'rate_limit', retryable: true, status: 429 };
|
|
32
|
+
}
|
|
33
|
+
if (code === 'server_error' || code === 'vector_store_timeout') {
|
|
34
|
+
return {
|
|
35
|
+
...(code ? { code } : {}),
|
|
36
|
+
providerErrorType: 'internal',
|
|
37
|
+
retryable: true,
|
|
38
|
+
status: 503
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (isOpenAIResponsesInvalidRequestCode(code)) {
|
|
42
|
+
return { code, providerErrorType: 'invalid_request', retryable: false, status: 400 };
|
|
43
|
+
}
|
|
44
|
+
return { ...(code ? { code } : {}), providerErrorType: 'internal', retryable: true, status: 503 };
|
|
45
|
+
}
|
|
46
|
+
function throwOpenAIResponsesStreamError(error) {
|
|
47
|
+
throw new OpenAIResponsesStreamError(openAIResponsesStreamErrorDetails(error));
|
|
48
|
+
}
|
|
8
49
|
/** Fast deterministic hash to shorten long strings */
|
|
9
50
|
function shortHash(str) {
|
|
10
51
|
let h1 = 0xdeadbeef;
|
|
@@ -18,6 +59,18 @@ function shortHash(str) {
|
|
|
18
59
|
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
19
60
|
return (h2 >>> 0).toString(36) + (h1 >>> 0).toString(36);
|
|
20
61
|
}
|
|
62
|
+
function readNumericTokenDetail(details, keys) {
|
|
63
|
+
if (!details || typeof details !== 'object')
|
|
64
|
+
return 0;
|
|
65
|
+
const record = details;
|
|
66
|
+
for (const key of keys) {
|
|
67
|
+
const value = record[key];
|
|
68
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
21
74
|
// =============================================================================
|
|
22
75
|
// Message conversion
|
|
23
76
|
// =============================================================================
|
|
@@ -383,14 +436,21 @@ export async function processResponsesStream(openaiStream, output, stream, model
|
|
|
383
436
|
else if (event.type === 'response.completed') {
|
|
384
437
|
const response = event.response;
|
|
385
438
|
if (response?.usage) {
|
|
386
|
-
const
|
|
439
|
+
const inputDetails = response.usage.input_tokens_details;
|
|
440
|
+
const cachedTokens = readNumericTokenDetail(inputDetails, ['cached_tokens']);
|
|
441
|
+
const cacheWriteTokens = readNumericTokenDetail(inputDetails, [
|
|
442
|
+
'cache_write_tokens',
|
|
443
|
+
'cache_creation_tokens',
|
|
444
|
+
'cache_write_input_tokens',
|
|
445
|
+
'cache_creation_input_tokens'
|
|
446
|
+
]);
|
|
387
447
|
output.usage = {
|
|
388
448
|
// OpenAI includes cached tokens in input_tokens; cost calculation
|
|
389
449
|
// derives uncached input from input - cacheRead - cacheWrite.
|
|
390
450
|
input: response.usage.input_tokens || 0,
|
|
391
451
|
output: response.usage.output_tokens || 0,
|
|
392
452
|
cacheRead: cachedTokens,
|
|
393
|
-
cacheWrite:
|
|
453
|
+
cacheWrite: cacheWriteTokens,
|
|
394
454
|
totalTokens: response.usage.total_tokens || 0,
|
|
395
455
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
|
|
396
456
|
};
|
|
@@ -398,7 +458,7 @@ export async function processResponsesStream(openaiStream, output, stream, model
|
|
|
398
458
|
calculateCost(model, output.usage);
|
|
399
459
|
if (options?.applyServiceTierPricing) {
|
|
400
460
|
const serviceTier = response?.service_tier ?? options.serviceTier;
|
|
401
|
-
options.applyServiceTierPricing(output.usage, serviceTier
|
|
461
|
+
options.applyServiceTierPricing(output.usage, serviceTier);
|
|
402
462
|
}
|
|
403
463
|
// Map status to stop reason
|
|
404
464
|
output.stopReason = mapStopReason(response?.status);
|
|
@@ -407,10 +467,13 @@ export async function processResponsesStream(openaiStream, output, stream, model
|
|
|
407
467
|
}
|
|
408
468
|
}
|
|
409
469
|
else if (event.type === 'error') {
|
|
410
|
-
|
|
470
|
+
throwOpenAIResponsesStreamError({
|
|
471
|
+
code: event.code,
|
|
472
|
+
message: event.message
|
|
473
|
+
});
|
|
411
474
|
}
|
|
412
475
|
else if (event.type === 'response.failed') {
|
|
413
|
-
|
|
476
|
+
throwOpenAIResponsesStreamError(event.response.error);
|
|
414
477
|
}
|
|
415
478
|
}
|
|
416
479
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ResponseCreateParamsStreaming } from 'openai/resources/responses/responses.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { SimpleStreamOptions, StreamFunction, StreamOptions } from '../types.js';
|
|
3
3
|
export interface OpenAIResponsesOptions extends StreamOptions {
|
|
4
4
|
reasoningEffort?: 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
|
|
5
5
|
reasoningSummary?: 'auto' | 'detailed' | 'concise' | null;
|
|
@@ -10,5 +10,5 @@ export interface OpenAIResponsesOptions extends StreamOptions {
|
|
|
10
10
|
*/
|
|
11
11
|
export declare const streamOpenAIResponses: StreamFunction<'openai-responses', OpenAIResponsesOptions>;
|
|
12
12
|
export declare const streamSimpleOpenAIResponses: StreamFunction<'openai-responses', SimpleStreamOptions>;
|
|
13
|
-
export declare function getServiceTierCostMultiplier(serviceTier: ResponseCreateParamsStreaming['service_tier'] | undefined
|
|
13
|
+
export declare function getServiceTierCostMultiplier(serviceTier: ResponseCreateParamsStreaming['service_tier'] | undefined): number;
|
|
14
14
|
//# sourceMappingURL=openai-responses.d.ts.map
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
|
-
import { SHORTCUT_MODEL_ID } from '../../model-ids.js';
|
|
3
2
|
import { getEnvApiKey } from '../env-api-keys.js';
|
|
4
3
|
import { supportsXhigh } from '../models.js';
|
|
5
4
|
import { AssistantMessageEventStream } from '../utils/event-stream.js';
|
|
@@ -98,9 +97,11 @@ export const streamSimpleOpenAIResponses = (model, context, options) => {
|
|
|
98
97
|
const reasoningEffort = supportsXhigh(model)
|
|
99
98
|
? options?.reasoning
|
|
100
99
|
: clampReasoning(options?.reasoning);
|
|
100
|
+
const serviceTier = options?.serviceTier;
|
|
101
101
|
return streamOpenAIResponses(model, context, {
|
|
102
102
|
...base,
|
|
103
|
-
reasoningEffort
|
|
103
|
+
reasoningEffort,
|
|
104
|
+
serviceTier
|
|
104
105
|
});
|
|
105
106
|
};
|
|
106
107
|
function createClient(model, context, apiKey, optionsHeaders, fetchOptions) {
|
|
@@ -170,20 +171,18 @@ function buildParams(model, context, options) {
|
|
|
170
171
|
}
|
|
171
172
|
return params;
|
|
172
173
|
}
|
|
173
|
-
export function getServiceTierCostMultiplier(serviceTier
|
|
174
|
+
export function getServiceTierCostMultiplier(serviceTier) {
|
|
174
175
|
switch (serviceTier) {
|
|
175
176
|
case 'flex':
|
|
176
177
|
return 0.5;
|
|
177
178
|
case 'priority':
|
|
178
|
-
if (model.id === SHORTCUT_MODEL_ID.Gpt55)
|
|
179
|
-
return 2.5;
|
|
180
179
|
return 2;
|
|
181
180
|
default:
|
|
182
181
|
return 1;
|
|
183
182
|
}
|
|
184
183
|
}
|
|
185
|
-
function applyServiceTierPricing(usage, serviceTier
|
|
186
|
-
const multiplier = getServiceTierCostMultiplier(serviceTier
|
|
184
|
+
function applyServiceTierPricing(usage, serviceTier) {
|
|
185
|
+
const multiplier = getServiceTierCostMultiplier(serviceTier);
|
|
187
186
|
if (multiplier === 1)
|
|
188
187
|
return;
|
|
189
188
|
usage.cost.input *= multiplier;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LLMErrorMetadataHeaders, parseStreamErrorPayload, safeModelStreamErrorMessage } from '../../contracts/model-stream.js';
|
|
1
|
+
import { applyModelAccessDeniedErrorDetails, isShortcutXLModelStreamErrorDetails, LLMErrorMetadataHeaders, parseStreamErrorPayload, safeModelStreamErrorMessage } from '../../contracts/model-stream.js';
|
|
2
2
|
import { isContextOverflowErrorMessage } from '../utils/overflow.js';
|
|
3
3
|
function getErrorMessage(error) {
|
|
4
4
|
return error instanceof Error ? error.message : (JSON.stringify(error) ?? String(error));
|
|
@@ -30,14 +30,24 @@ function normalizeApiV2ErrorDetails(error, rawMessage) {
|
|
|
30
30
|
const details = parseStreamErrorPayload(payload, status) ?? (status ? { status } : undefined);
|
|
31
31
|
if (!details)
|
|
32
32
|
return undefined;
|
|
33
|
+
const accessAwareDetails = applyModelAccessDeniedErrorDetails(details, rawMessage);
|
|
33
34
|
if (isContextOverflowErrorMessage(rawMessage)) {
|
|
34
|
-
return { ...
|
|
35
|
+
return { ...accessAwareDetails, code: 'context_overflow' };
|
|
35
36
|
}
|
|
36
|
-
return
|
|
37
|
+
return accessAwareDetails;
|
|
38
|
+
}
|
|
39
|
+
function streamDetailsFromError(error) {
|
|
40
|
+
const detailsError = error;
|
|
41
|
+
if (isShortcutXLModelStreamErrorDetails(detailsError.details))
|
|
42
|
+
return detailsError.details;
|
|
43
|
+
if (isShortcutXLModelStreamErrorDetails(detailsError.errorDetails))
|
|
44
|
+
return detailsError.errorDetails;
|
|
45
|
+
return undefined;
|
|
37
46
|
}
|
|
38
47
|
export function applyNormalizedProviderError(output, error) {
|
|
39
48
|
const rawMessage = getErrorMessage(error);
|
|
40
|
-
const details =
|
|
49
|
+
const details = streamDetailsFromError(error) ??
|
|
50
|
+
normalizeApiV2ErrorDetails(error, rawMessage);
|
|
41
51
|
if (!details) {
|
|
42
52
|
output.errorMessage = rawMessage;
|
|
43
53
|
return { usedStructuredDetails: false };
|
|
@@ -129,7 +129,9 @@ export interface AgentSessionConfig {
|
|
|
129
129
|
info?: (input: {
|
|
130
130
|
module: string;
|
|
131
131
|
event: string;
|
|
132
|
+
analyticsEvent?: boolean;
|
|
132
133
|
data?: Record<string, unknown>;
|
|
134
|
+
duration?: number;
|
|
133
135
|
}) => void;
|
|
134
136
|
};
|
|
135
137
|
}
|
|
@@ -187,6 +189,7 @@ export declare class AgentSession {
|
|
|
187
189
|
private _summaryEmitter;
|
|
188
190
|
private _toolSummaryDiagnostics;
|
|
189
191
|
private _observability?;
|
|
192
|
+
private _nextCompactionOpId;
|
|
190
193
|
private _tools;
|
|
191
194
|
private _models;
|
|
192
195
|
private _fastModeAllowed;
|
|
@@ -481,8 +484,8 @@ export declare class AgentSession {
|
|
|
481
484
|
supportsXhighThinking(): boolean;
|
|
482
485
|
/** True if the active model exposes any thinking/reasoning configuration. */
|
|
483
486
|
supportsThinking(): boolean;
|
|
484
|
-
/** Check compaction decision and trigger if needed. */
|
|
485
487
|
private _triggerCompactionIfNeeded;
|
|
488
|
+
private _createCompactionObservabilityWrap;
|
|
486
489
|
/** Run a manual compaction pass over the conversation, optionally with custom instructions. */
|
|
487
490
|
compact(customInstructions?: string): Promise<CompactionResult>;
|
|
488
491
|
/** Cancel any in-flight compaction run. */
|
|
@@ -27,6 +27,7 @@ import { expandPromptTemplate } from './resources/prompt-template-expansion.js';
|
|
|
27
27
|
import { AUTONOMOUS_STATE_CUSTOM_TYPE, buildAutonomousRefreshPrompt, extractProgressFolder, inferAutonomousStateFromEntries } from './session/autonomous-workflow.js';
|
|
28
28
|
import { BranchManager } from './session/branch-manager.js';
|
|
29
29
|
import { parseBudgetCommand } from './session/budget-command.js';
|
|
30
|
+
import { createCompactionObservabilityWrap } from './session/compaction-observability.js';
|
|
30
31
|
import { emitExtensionEvent } from './session/extension-emitter.js';
|
|
31
32
|
import { ExtensionLifecycle } from './session/extension-lifecycle.js';
|
|
32
33
|
import { ModelManager } from './session/model-manager.js';
|
|
@@ -114,6 +115,7 @@ export class AgentSession {
|
|
|
114
115
|
_summaryEmitter;
|
|
115
116
|
_toolSummaryDiagnostics = [];
|
|
116
117
|
_observability;
|
|
118
|
+
_nextCompactionOpId = 0;
|
|
117
119
|
// Tool registry — owns tool maps, prompt snippets, system prompt rebuild
|
|
118
120
|
_tools;
|
|
119
121
|
// Model manager — model switching, cycling, thinking level
|
|
@@ -1378,15 +1380,18 @@ export class AgentSession {
|
|
|
1378
1380
|
supportsThinking() {
|
|
1379
1381
|
return supportsThinking(this.model);
|
|
1380
1382
|
}
|
|
1381
|
-
// =========================================================================
|
|
1382
|
-
// Compaction (delegated to SessionCompaction)
|
|
1383
|
-
// =========================================================================
|
|
1384
|
-
// =========================================================================
|
|
1385
1383
|
// Compaction (delegated to SessionCompaction + bridge factories)
|
|
1386
|
-
// =========================================================================
|
|
1387
|
-
/** Check compaction decision and trigger if needed. */
|
|
1388
1384
|
async _triggerCompactionIfNeeded(msg, skipAbortedCheck, opts) {
|
|
1389
|
-
return triggerCompactionIfNeeded(this._compactionTriggerDeps, msg, skipAbortedCheck,
|
|
1385
|
+
return triggerCompactionIfNeeded(this._compactionTriggerDeps, msg, skipAbortedCheck, {
|
|
1386
|
+
...opts,
|
|
1387
|
+
heartbeatWrap: this._createCompactionObservabilityWrap()
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
_createCompactionObservabilityWrap() {
|
|
1391
|
+
return createCompactionObservabilityWrap({
|
|
1392
|
+
observability: this._observability,
|
|
1393
|
+
nextOpId: () => `compaction_${++this._nextCompactionOpId}`
|
|
1394
|
+
});
|
|
1390
1395
|
}
|
|
1391
1396
|
/** Run a manual compaction pass over the conversation, optionally with custom instructions. */
|
|
1392
1397
|
async compact(customInstructions) {
|
|
@@ -1398,7 +1403,9 @@ export class AgentSession {
|
|
|
1398
1403
|
const apiKey = await this._modelRegistry.getApiKey(this.model);
|
|
1399
1404
|
if (!apiKey)
|
|
1400
1405
|
throw new Error(`No API key for ${this.model.provider}`);
|
|
1401
|
-
|
|
1406
|
+
const compact = this._compaction.runManual(this.model, apiKey, this.sessionManager.getBranch(), customInstructions, this._compactionActions, { userMessageId: getActiveUserMessageId(this.messages) });
|
|
1407
|
+
const wrap = this._createCompactionObservabilityWrap();
|
|
1408
|
+
return await (wrap ? wrap(compact, 'compaction', { label: 'Compacting context…' }) : compact);
|
|
1402
1409
|
}
|
|
1403
1410
|
finally {
|
|
1404
1411
|
this._reconnectToAgent();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { DEFAULT_THINKING_LEVEL, isValidThinkingLevel } from '../../core/defaults.js';
|
|
6
|
-
import { SHORTCUT_MODEL_ID, SHORTCUT_PROVIDER_ID } from '../../model-ids.js';
|
|
6
|
+
import { SHORTCUT_MODEL_ID, SHORTCUT_OPENAI_MODEL_ID, SHORTCUT_PROVIDER_ID } from '../../model-ids.js';
|
|
7
7
|
export function formatModelRef(model) {
|
|
8
8
|
return `${model.provider}/${model.id}`;
|
|
9
9
|
}
|
|
@@ -36,13 +36,13 @@ export function resolveExactModelRef(availableModels, requested) {
|
|
|
36
36
|
}
|
|
37
37
|
/** Default model IDs for each provider with a preferred initial model. */
|
|
38
38
|
export const defaultModelPerProvider = {
|
|
39
|
-
[SHORTCUT_PROVIDER_ID]:
|
|
39
|
+
[SHORTCUT_PROVIDER_ID]: SHORTCUT_OPENAI_MODEL_ID,
|
|
40
40
|
anthropic: SHORTCUT_MODEL_ID.ClaudeOpus48,
|
|
41
|
-
openai:
|
|
42
|
-
'azure-openai-responses':
|
|
41
|
+
openai: SHORTCUT_OPENAI_MODEL_ID,
|
|
42
|
+
'azure-openai-responses': SHORTCUT_OPENAI_MODEL_ID,
|
|
43
43
|
google: 'gemini-2.5-pro',
|
|
44
44
|
'google-vertex': 'gemini-3-pro-preview',
|
|
45
|
-
openrouter:
|
|
45
|
+
openrouter: `openai/${SHORTCUT_OPENAI_MODEL_ID}`,
|
|
46
46
|
'vercel-ai-gateway': 'anthropic/claude-opus-4-8',
|
|
47
47
|
xai: 'grok-4-fast-non-reasoning',
|
|
48
48
|
groq: 'openai/gpt-oss-120b',
|
|
@@ -56,7 +56,11 @@ export const defaultModelPerProvider = {
|
|
|
56
56
|
};
|
|
57
57
|
function getDefaultModelCandidatesForProvider(provider) {
|
|
58
58
|
if (provider === SHORTCUT_PROVIDER_ID) {
|
|
59
|
-
return [
|
|
59
|
+
return [
|
|
60
|
+
SHORTCUT_OPENAI_MODEL_ID,
|
|
61
|
+
SHORTCUT_MODEL_ID.ClaudeFable5,
|
|
62
|
+
SHORTCUT_MODEL_ID.ClaudeOpus48
|
|
63
|
+
];
|
|
60
64
|
}
|
|
61
65
|
return [defaultModelPerProvider[provider]].filter((modelId) => !!modelId);
|
|
62
66
|
}
|
|
@@ -271,7 +275,7 @@ export function resolveCliModel(options) {
|
|
|
271
275
|
}
|
|
272
276
|
// If we inferred a provider from the slash but found no match within that provider,
|
|
273
277
|
// fall back to matching the full input as a raw model id across all models.
|
|
274
|
-
// This handles OpenRouter-style IDs like "openai
|
|
278
|
+
// This handles OpenRouter-style IDs like "openai/<model>:extended" where "openai"
|
|
275
279
|
// looks like a provider but the full string is actually a model id on openrouter.
|
|
276
280
|
if (inferredProvider) {
|
|
277
281
|
const lower = cliModel.toLowerCase();
|
|
@@ -14,6 +14,7 @@ import { streamSimpleOpenAICompletions } from '../../ai/providers/openai-complet
|
|
|
14
14
|
import { streamSimpleOpenAIResponses } from '../../ai/providers/openai-responses.js';
|
|
15
15
|
import { NPM_PACKAGE_NAME, VERSION } from '../../config.js';
|
|
16
16
|
import { gatewayBaseUrlForTransport } from '../../embedded-agent/gateway-base-url.js';
|
|
17
|
+
import { OPENAI_PRIORITY_SERVICE_TIER, shortcutProviderFastModeForContext, shortcutSpeedFromOptions, withProviderFastModeHeaders, withProviderFastModePayloadOverride } from '../../embedded-agent/provider-fast-mode.js';
|
|
17
18
|
import { SHORTCUT_PROVIDER_ID } from '../../model-ids.js';
|
|
18
19
|
import { createShortcutModelDefinitions } from '../../shortcut-model-catalog.js';
|
|
19
20
|
import { shortcutOAuth } from '../auth/shortcut-oauth.js';
|
|
@@ -72,11 +73,15 @@ function createShortcutApiV2AttributionHeaders(options) {
|
|
|
72
73
|
if (attribution?.fileAttachmentIds?.length) {
|
|
73
74
|
headers['X-File-Attachment-Ids'] = attribution.fileAttachmentIds.join(',');
|
|
74
75
|
}
|
|
75
|
-
if (attribution?.speed === 'fast' || attribution?.speed === 'standard') {
|
|
76
|
-
headers['X-Shortcut-Speed'] = attribution.speed;
|
|
77
|
-
}
|
|
78
76
|
return headers;
|
|
79
77
|
}
|
|
78
|
+
function providerFastModeKindForTransport(transport) {
|
|
79
|
+
if (transport === 'anthropic-messages')
|
|
80
|
+
return 'anthropic';
|
|
81
|
+
if (transport === 'openai-completions' || transport === 'openai-responses')
|
|
82
|
+
return 'openai';
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
80
85
|
/**
|
|
81
86
|
* Dispatch a Shortcut model to its provider-native stream function, routed at
|
|
82
87
|
* the api-v2 gateway with Shortcut auth. `options.apiKey` is the Shortcut
|
|
@@ -110,10 +115,21 @@ export function createShortcutApiV2Dispatcher(getExtraHeaders) {
|
|
|
110
115
|
api: transport,
|
|
111
116
|
baseUrl: gatewayBaseUrlForTransport(model.baseUrl, transport)
|
|
112
117
|
};
|
|
113
|
-
|
|
118
|
+
const providerFastModeKind = providerFastModeKindForTransport(transport);
|
|
119
|
+
const fastMode = providerFastModeKind
|
|
120
|
+
? shortcutProviderFastModeForContext({
|
|
121
|
+
context,
|
|
122
|
+
modelId: model.id,
|
|
123
|
+
provider: providerFastModeKind,
|
|
124
|
+
speed: shortcutSpeedFromOptions(options)
|
|
125
|
+
})
|
|
126
|
+
: undefined;
|
|
127
|
+
const streamOptions = withProviderFastModePayloadOverride({
|
|
114
128
|
...options,
|
|
115
|
-
headers: { ...authHeaders, ...options?.headers }
|
|
116
|
-
|
|
129
|
+
headers: withProviderFastModeHeaders({ ...authHeaders, ...options?.headers }, fastMode),
|
|
130
|
+
...(fastMode === 'openai' ? { serviceTier: OPENAI_PRIORITY_SERVICE_TIER } : {})
|
|
131
|
+
}, fastMode);
|
|
132
|
+
return streamFn(nativeModel, context, streamOptions);
|
|
117
133
|
};
|
|
118
134
|
}
|
|
119
135
|
/** Register the Shortcut provider, routing all models through the api-v2 gateway. */
|
|
@@ -118,8 +118,6 @@ function createShortcutLlmProxyHeaders(request, attribution) {
|
|
|
118
118
|
headers['X-Turn-Id'] = attribution.userMessageId;
|
|
119
119
|
if (request.llmOperation)
|
|
120
120
|
headers['X-LLM-Operation'] = request.llmOperation;
|
|
121
|
-
if (request.speed)
|
|
122
|
-
headers['X-Shortcut-Speed'] = request.speed;
|
|
123
121
|
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
124
122
|
}
|
|
125
123
|
/**
|
|
@@ -148,7 +146,8 @@ export function createShortcutLlmProxyClient(options) {
|
|
|
148
146
|
apiKey,
|
|
149
147
|
maxTokens: request.max_tokens ?? request.max_output_tokens,
|
|
150
148
|
signal: callOptions?.signal,
|
|
151
|
-
headers
|
|
149
|
+
headers,
|
|
150
|
+
...(request.speed !== undefined ? { speed: request.speed } : {})
|
|
152
151
|
});
|
|
153
152
|
const text = await collectAssistantText(stream);
|
|
154
153
|
if (!text.trim()) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HeartbeatWrapFn } from '../../core/agent-engine.js';
|
|
2
|
+
export interface CompactionObservability {
|
|
3
|
+
info?: (input: {
|
|
4
|
+
module: string;
|
|
5
|
+
event: string;
|
|
6
|
+
analyticsEvent?: boolean;
|
|
7
|
+
data?: Record<string, unknown>;
|
|
8
|
+
duration?: number;
|
|
9
|
+
}) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface CreateCompactionObservabilityWrapOptions {
|
|
12
|
+
nextOpId: () => string;
|
|
13
|
+
now?: () => number;
|
|
14
|
+
observability?: CompactionObservability;
|
|
15
|
+
}
|
|
16
|
+
export declare function createCompactionObservabilityWrap(options: CreateCompactionObservabilityWrapOptions): HeartbeatWrapFn | undefined;
|
|
17
|
+
//# sourceMappingURL=compaction-observability.d.ts.map
|