@ottocode/sdk 0.1.242 → 0.1.244

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.
@@ -29,32 +29,6 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
29
29
  output: 0,
30
30
  },
31
31
  },
32
- {
33
- id: 'codex-mini-latest',
34
- ownedBy: 'openai',
35
- label: 'Codex Mini',
36
- modalities: {
37
- input: ['text'],
38
- output: ['text'],
39
- },
40
- toolCall: true,
41
- reasoningText: true,
42
- attachment: true,
43
- temperature: false,
44
- knowledge: '2024-04',
45
- releaseDate: '2025-05-16',
46
- lastUpdated: '2025-05-16',
47
- openWeights: false,
48
- cost: {
49
- input: 1.5,
50
- output: 6,
51
- cacheRead: 0.375,
52
- },
53
- limit: {
54
- context: 200000,
55
- output: 100000,
56
- },
57
- },
58
32
  {
59
33
  id: 'gpt-3.5-turbo',
60
34
  ownedBy: 'openai',
@@ -1752,7 +1726,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
1752
1726
  reasoningText: true,
1753
1727
  attachment: true,
1754
1728
  temperature: true,
1755
- knowledge: '2025-05',
1729
+ knowledge: '2025-05-31',
1756
1730
  releaseDate: '2026-02-05',
1757
1731
  lastUpdated: '2026-03-13',
1758
1732
  openWeights: false,
@@ -1778,8 +1752,8 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
1778
1752
  toolCall: true,
1779
1753
  reasoningText: true,
1780
1754
  attachment: true,
1781
- temperature: true,
1782
- knowledge: '2026-01',
1755
+ temperature: false,
1756
+ knowledge: '2026-01-31',
1783
1757
  releaseDate: '2026-04-16',
1784
1758
  lastUpdated: '2026-04-16',
1785
1759
  openWeights: false,
@@ -1914,7 +1888,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
1914
1888
  reasoningText: true,
1915
1889
  attachment: true,
1916
1890
  temperature: true,
1917
- knowledge: '2025-08',
1891
+ knowledge: '2025-08-31',
1918
1892
  releaseDate: '2026-02-17',
1919
1893
  lastUpdated: '2026-03-13',
1920
1894
  openWeights: false,
@@ -2345,7 +2319,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
2345
2319
  cost: {
2346
2320
  input: 1.25,
2347
2321
  output: 10,
2348
- cacheRead: 0.31,
2322
+ cacheRead: 0.125,
2349
2323
  },
2350
2324
  limit: {
2351
2325
  context: 1048576,
@@ -3060,7 +3034,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
3060
3034
  reasoningText: true,
3061
3035
  attachment: true,
3062
3036
  temperature: true,
3063
- knowledge: '2025-05-30',
3037
+ knowledge: '2025-05-31',
3064
3038
  releaseDate: '2026-02-05',
3065
3039
  lastUpdated: '2026-02-05',
3066
3040
  openWeights: false,
@@ -3075,6 +3049,33 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
3075
3049
  output: 128000,
3076
3050
  },
3077
3051
  },
3052
+ {
3053
+ id: 'anthropic/claude-opus-4.7',
3054
+ ownedBy: 'anthropic',
3055
+ label: 'Claude Opus 4.7',
3056
+ modalities: {
3057
+ input: ['text', 'image', 'pdf'],
3058
+ output: ['text'],
3059
+ },
3060
+ toolCall: true,
3061
+ reasoningText: true,
3062
+ attachment: true,
3063
+ temperature: false,
3064
+ knowledge: '2026-01-31',
3065
+ releaseDate: '2026-04-16',
3066
+ lastUpdated: '2026-04-16',
3067
+ openWeights: false,
3068
+ cost: {
3069
+ input: 5,
3070
+ output: 25,
3071
+ cacheRead: 0.5,
3072
+ cacheWrite: 6.25,
3073
+ },
3074
+ limit: {
3075
+ context: 1000000,
3076
+ output: 128000,
3077
+ },
3078
+ },
3078
3079
  {
3079
3080
  id: 'anthropic/claude-sonnet-4',
3080
3081
  ownedBy: 'anthropic',
@@ -3141,6 +3142,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
3141
3142
  reasoningText: true,
3142
3143
  attachment: true,
3143
3144
  temperature: true,
3145
+ knowledge: '2025-08-31',
3144
3146
  releaseDate: '2026-02-17',
3145
3147
  lastUpdated: '2026-02-17',
3146
3148
  openWeights: false,
@@ -3687,7 +3689,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
3687
3689
  cost: {
3688
3690
  input: 1.25,
3689
3691
  output: 10,
3690
- cacheRead: 0.31,
3692
+ cacheRead: 0.125,
3691
3693
  },
3692
3694
  limit: {
3693
3695
  context: 1048576,
@@ -7296,9 +7298,9 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
7296
7298
  reasoningText: true,
7297
7299
  attachment: true,
7298
7300
  temperature: true,
7299
- knowledge: '2025-08-31',
7301
+ knowledge: '2025-05-31',
7300
7302
  releaseDate: '2026-02-05',
7301
- lastUpdated: '2026-02-05',
7303
+ lastUpdated: '2026-03-13',
7302
7304
  openWeights: false,
7303
7305
  cost: {
7304
7306
  input: 5,
@@ -7325,8 +7327,8 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
7325
7327
  toolCall: true,
7326
7328
  reasoningText: true,
7327
7329
  attachment: true,
7328
- temperature: true,
7329
- knowledge: '2026-01',
7330
+ temperature: false,
7331
+ knowledge: '2026-01-31',
7330
7332
  releaseDate: '2026-04-16',
7331
7333
  lastUpdated: '2026-04-16',
7332
7334
  openWeights: false,
@@ -7416,7 +7418,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
7416
7418
  reasoningText: true,
7417
7419
  attachment: true,
7418
7420
  temperature: true,
7419
- knowledge: '2025-07-31',
7421
+ knowledge: '2025-08-31',
7420
7422
  releaseDate: '2026-02-17',
7421
7423
  lastUpdated: '2026-02-17',
7422
7424
  openWeights: false,
@@ -9689,7 +9691,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
9689
9691
  reasoningText: true,
9690
9692
  attachment: true,
9691
9693
  temperature: true,
9692
- knowledge: '2025-03-31',
9694
+ knowledge: '2025-05-31',
9693
9695
  releaseDate: '2026-02-05',
9694
9696
  lastUpdated: '2026-02-05',
9695
9697
  openWeights: false,
@@ -9702,6 +9704,31 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
9702
9704
  output: 64000,
9703
9705
  },
9704
9706
  },
9707
+ {
9708
+ id: 'claude-opus-4.7',
9709
+ ownedBy: 'anthropic',
9710
+ label: 'Claude Opus 4.7',
9711
+ modalities: {
9712
+ input: ['text', 'image'],
9713
+ output: ['text'],
9714
+ },
9715
+ toolCall: true,
9716
+ reasoningText: true,
9717
+ attachment: true,
9718
+ temperature: false,
9719
+ knowledge: '2026-01-31',
9720
+ releaseDate: '2026-04-16',
9721
+ lastUpdated: '2026-04-16',
9722
+ openWeights: false,
9723
+ cost: {
9724
+ input: 0,
9725
+ output: 0,
9726
+ },
9727
+ limit: {
9728
+ context: 144000,
9729
+ output: 64000,
9730
+ },
9731
+ },
9705
9732
  {
9706
9733
  id: 'claude-opus-41',
9707
9734
  ownedBy: 'anthropic',
@@ -9789,6 +9816,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
9789
9816
  reasoningText: true,
9790
9817
  attachment: true,
9791
9818
  temperature: true,
9819
+ knowledge: '2025-08-31',
9792
9820
  releaseDate: '2026-02-17',
9793
9821
  lastUpdated: '2026-02-17',
9794
9822
  openWeights: false,
@@ -60,12 +60,16 @@ function parseYamlFrontmatter(
60
60
  const key = normalizeKey(trimmed.slice(0, colonIdx).trim());
61
61
  const value = trimmed.slice(colonIdx + 1).trim();
62
62
 
63
- if (value === '|' || value === '>') {
63
+ // Handle YAML block scalars with optional chomping/indentation indicators:
64
+ // `|`, `>`, `|-`, `>-`, `|+`, `>+`, `|2`, `>2-`, etc.
65
+ const blockScalarMatch = value.match(/^([|>])[-+]?\d*[-+]?$/);
66
+ if (blockScalarMatch) {
67
+ const style = blockScalarMatch[1] as '|' | '>';
64
68
  const { content, nextIndex } = readBlockScalar(
65
69
  lines,
66
70
  index + 1,
67
71
  indent,
68
- value,
72
+ style,
69
73
  );
70
74
  result[key] = content;
71
75
  index = nextIndex;
@@ -142,8 +146,10 @@ function readNestedObject(
142
146
  const key = normalizeKey(trimmed.slice(0, colonIdx).trim());
143
147
  const value = trimmed.slice(colonIdx + 1).trim();
144
148
 
145
- if (value === '|' || value === '>') {
146
- const block = readBlockScalar(lines, index + 1, indent, value);
149
+ const blockScalarMatch = value.match(/^([|>])[-+]?\d*[-+]?$/);
150
+ if (blockScalarMatch) {
151
+ const style = blockScalarMatch[1] as '|' | '>';
152
+ const block = readBlockScalar(lines, index + 1, indent, style);
147
153
  result[key] = block.content;
148
154
  index = block.nextIndex;
149
155
  continue;
@@ -120,32 +120,53 @@ async function loadSubFile(
120
120
 
121
121
  function buildSkillDescription(): string {
122
122
  if (cachedSkillList.length === 0) {
123
- return 'Load a skill by name. No skills are currently available.';
123
+ return 'Load a skill by name to get detailed, task-specific instructions. No skills are currently available.';
124
124
  }
125
125
 
126
- const skillsXml = cachedSkillList
126
+ // Dedupe by name — later scopes (cwd > repo > user) have already overwritten
127
+ // earlier ones in the loader, so the cached list is deduplicated. We re-dedupe
128
+ // defensively here in case the same name slipped through from different dirs.
129
+ const seen = new Set<string>();
130
+ const unique: DiscoveredSkill[] = [];
131
+ for (const s of cachedSkillList) {
132
+ const key = s.name.trim();
133
+ if (!key || seen.has(key)) continue;
134
+ seen.add(key);
135
+ unique.push(s);
136
+ }
137
+ unique.sort((a, b) => a.name.localeCompare(b.name));
138
+
139
+ const skillsXml = unique
127
140
  .map(
128
141
  (s) =>
129
- `<skill><name>${escapeXml(s.name)}</name><description>${escapeXml(s.description)}</description></skill>`,
142
+ `<skill><name>${escapeXml(s.name)}</name><description>${escapeXml(summarizeDescription(s.description))}</description><location>${escapeXml(s.scope)}</location></skill>`,
130
143
  )
131
144
  .join('\n');
132
145
 
133
- return `Load a skill by name to get detailed instructions.
146
+ return `Load a skill by name to get detailed, task-specific instructions.
134
147
 
135
- <available_skills>
136
- ${skillsXml}
137
- </available_skills>
148
+ <skills_instructions>
149
+ When the user's request matches one of the available skills below, call this tool with the skill name to load its full instructions. Skills provide specialized capabilities and domain knowledge.
138
150
 
139
- Call this tool with the skill name when you need the full instructions.
140
- If the skill references sub-files (e.g. rules/animations.md), load them with the \`file\` parameter:
141
- skill({ name: "skill-name", file: "rules/animations.md" })
151
+ How to use skills:
152
+ - Invoke with \`skill({ name: "<skill-name>" })\` only the name is required.
153
+ - The response contains the skill's full body plus \`availableFiles\` for sub-files.
154
+ - For sub-files: \`skill({ name: "<skill-name>", file: "rules/animations.md" })\`.
142
155
 
143
- The response includes \`availableFiles\` listing all loadable sub-files in the skill directory.
144
- If \`securityNotices\` are present, review them they flag hidden content (HTML comments, invisible characters, etc.) that may not be visible when reading the markdown normally.`;
156
+ Rules:
157
+ - Only invoke skills listed in <available_skills> below.
158
+ - Do NOT invoke speculatively. Only call when the user's request clearly matches a skill's description or trigger phrases.
159
+ - Do NOT invoke the same skill twice in one turn.
160
+ - If a skill response includes \`securityNotices\`, review them — they flag hidden content (HTML comments, invisible characters, etc.) that may not render visibly.
161
+ </skills_instructions>
162
+
163
+ <available_skills>
164
+ ${skillsXml}
165
+ </available_skills>`;
145
166
  }
146
167
 
147
168
  function escapeXml(str: string): string {
148
- return str
169
+ return String(str)
149
170
  .replace(/&/g, '&amp;')
150
171
  .replace(/</g, '&lt;')
151
172
  .replace(/>/g, '&gt;')
@@ -153,6 +174,63 @@ function escapeXml(str: string): string {
153
174
  .replace(/'/g, '&apos;');
154
175
  }
155
176
 
177
+ // Condense a SKILL.md description to "what it does + when to use it".
178
+ //
179
+ // Most frontmatter follows one of two shapes:
180
+ // A. "<what>. Use when <triggers>. Also use when … For X, see Y."
181
+ // B. "When the user wants X. Also use when <mega trigger list>. For Y, see Z."
182
+ //
183
+ // Rule: keep up to two sentences, but STOP early at cues that mark pure
184
+ // trigger-list expansion or see-also chatter ("Also use when …", "For X, see Y",
185
+ // "Use this …"). Sentence 1 is always kept. Sentence 2 is kept only if it's a
186
+ // "Use when …" clause (trigger phrases the model actually needs) — not an
187
+ // "Also use when" balloon.
188
+ const SENTENCE_END = /[.!?]\s/g;
189
+ const SKIP_PREFIXES = [
190
+ /^also use when\b/i,
191
+ /^for [^.]*?,?\s*see\b/i,
192
+ /^see\s+/i,
193
+ /^use this\b/i,
194
+ /^use proactively\b/i,
195
+ /^distinct from\b/i,
196
+ /^different from\b/i,
197
+ /^not for\b/i,
198
+ ];
199
+
200
+ function shouldSkipSentence(sentence: string): boolean {
201
+ const s = sentence.trim();
202
+ return SKIP_PREFIXES.some((re) => re.test(s));
203
+ }
204
+
205
+ export function summarizeDescription(raw: string): string {
206
+ const text = String(raw ?? '')
207
+ .replace(/\s+/g, ' ')
208
+ .trim();
209
+ if (!text) return '';
210
+
211
+ // Split into sentences, keeping terminal punctuation.
212
+ const sentences: string[] = [];
213
+ SENTENCE_END.lastIndex = 0;
214
+ let lastIndex = 0;
215
+ let match: RegExpExecArray | null;
216
+ // biome-ignore lint/suspicious/noAssignInExpressions: standard regex iteration
217
+ while ((match = SENTENCE_END.exec(text)) !== null) {
218
+ sentences.push(text.slice(lastIndex, match.index + 1).trim());
219
+ lastIndex = match.index + match[0].length;
220
+ }
221
+ if (lastIndex < text.length) sentences.push(text.slice(lastIndex).trim());
222
+
223
+ if (sentences.length === 0) return text;
224
+
225
+ const kept: string[] = [sentences[0]]; // always keep sentence 1 (what)
226
+ // Keep sentence 2 only if it adds routing signal (e.g. starts with "Use when").
227
+ if (sentences[1] && !shouldSkipSentence(sentences[1])) {
228
+ kept.push(sentences[1]);
229
+ }
230
+
231
+ return kept.join(' ');
232
+ }
233
+
156
234
  export function rebuildSkillDescription(): string {
157
235
  return buildSkillDescription();
158
236
  }