Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/pink-skills-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-claude-code': minor
---

Expose Claude Code setting sources and loaded skills in session metadata.
7 changes: 6 additions & 1 deletion packages/ai-claude-code/src/adapters/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export type ClaudeCodePermissionMode =
| 'bypassPermissions'
| 'plan'

export type ClaudeCodeSettingSource = 'user' | 'project' | 'local'

const DEFAULT_WORKDIR = '/workspace'

export interface ClaudeCodeTextConfig {
Expand All @@ -85,6 +87,8 @@ export interface ClaudeCodeTextConfig {
addDirs?: Array<string>
/** Maximum harness-internal turns (`--max-turns`). */
maxTurns?: number
/** Claude Code filesystem settings loaded via `--setting-sources`. Defaults to `['user']`. */
settingSources?: Array<ClaudeCodeSettingSource>
/**
* How `systemPrompts` from `chat()` are applied:
* - `'append'` (default): `--append-system-prompt` on top of the preset.
Expand Down Expand Up @@ -208,6 +212,7 @@ export class ClaudeCodeTextAdapter<
const config = this.adapterConfig
const modelOptions = options.modelOptions
const exeParts = (config.claudeExecutable ?? 'claude').split(' ')
const settingSources = config.settingSources ?? ['user']

// `--setting-sources user` before `-p`. Do not pass `--bare`: that flag
// skips stored `claude login` credentials and prints
Expand All @@ -216,7 +221,7 @@ export class ClaudeCodeTextAdapter<
const args: Array<string> = [
...exeParts,
'--setting-sources',
'user',
settingSources.join(','),
'-p',
'--output-format',
'stream-json',
Expand Down
1 change: 1 addition & 0 deletions packages/ai-claude-code/src/stream/sdk-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface SdkInitMessage {
session_id: string
model: string
tools: Array<string>
skills?: Array<string>
cwd?: string
}

Expand Down
1 change: 1 addition & 0 deletions packages/ai-claude-code/src/stream/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,7 @@ export async function* translateSdkStream(
sessionId: sdkMessage.session_id,
model: sdkMessage.model,
tools: sdkMessage.tools,
skills: sdkMessage.skills ?? [],
},
}
continue
Expand Down
41 changes: 40 additions & 1 deletion packages/ai-claude-code/tests/text-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ describe('claude-code in-sandbox adapter', () => {
claudeExecutable: 'node fake-claude.mjs',
streamPartials: false,
emitDiff: false,
settingSources: ['project', 'local'],
Comment thread
coderabbitai[bot] marked this conversation as resolved.
})

const chunks = await collect(
Expand All @@ -200,7 +201,7 @@ describe('claude-code in-sandbox adapter', () => {
const argv = await sbx.fs.read('/workspace/argv.txt')
expect(argv).not.toContain('--bare')
expect(argv).toContain('--setting-sources')
expect(argv).toContain('user')
expect(argv).toContain('project,local')
expect(argv).toContain('--json-schema')
expect(argv).toContain('"type":"object"')
expect(argv).toContain('"summary"')
Expand All @@ -220,6 +221,44 @@ describe('claude-code in-sandbox adapter', () => {
await sbx.destroy()
})

it('uses user as the default setting source', async () => {
const fake = [
`import { writeFileSync } from 'node:fs'`,
`writeFileSync('argv.txt', process.argv.slice(2).join(' '))`,
`let input = ''`,
`process.stdin.on('data', (d) => { input += d })`,
`process.stdin.on('end', () => {`,
` const w = (o) => process.stdout.write(JSON.stringify(o) + '\\n')`,
` w({ type: 'system', subtype: 'init', session_id: 'sess-default', model: 'haiku', tools: [] })`,
` w({ type: 'result', subtype: 'success', result: 'ok', usage: { input_tokens: 1, output_tokens: 1 } })`,
`})`,
].join('\n')
const sbx = await provider.create({})
await sbx.fs.write('/workspace/fake-claude.mjs', fake)

try {
const adapter = claudeCodeText('haiku', {
claudeExecutable: 'node fake-claude.mjs',
streamPartials: false,
emitDiff: false,
})
await collect(
adapter.chatStream({
model: 'haiku',
messages: [{ role: 'user', content: 'hi' }],
logger: noopLogger,
capabilities: capabilityContextWith(sbx),
}),
)

expect(await sbx.fs.read('/workspace/argv.txt')).toMatch(
/--setting-sources user(?:\s|$)/,
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} finally {
await sbx.destroy()
}
})

it('copies ANTHROPIC_API_KEY from the host process into the CLI env', async () => {
const fake = [
`import { writeFileSync } from 'node:fs'`,
Expand Down
13 changes: 13 additions & 0 deletions packages/ai-claude-code/tests/translate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const init: AgentSdkMessage = {
session_id: 'sess-abc',
model: 'claude-opus-4-6',
tools: ['Bash', 'Read'],
skills: ['repo-search'],
cwd: '/tmp',
}

Expand Down Expand Up @@ -106,10 +107,22 @@ describe('translateSdkStream', () => {
sessionId: 'sess-abc',
model: 'claude-opus-4-6',
tools: ['Bash', 'Read'],
skills: ['repo-search'],
},
})
})

it('uses an empty skill list when the SDK init message omits skills', async () => {
const { skills: _skills, ...initWithoutSkills } = init
const chunks = await collect([
initWithoutSkills,
assistantText('hi'),
resultSuccess,
])
const custom = chunks.find((c) => c.type === 'CUSTOM')
expect(custom).toMatchObject({ value: { skills: [] } })
})

it('maps usage onto RUN_FINISHED including cache token details', async () => {
const chunks = await collect([init, assistantText('hi'), resultSuccess])
const finished = chunks.find((c) => c.type === 'RUN_FINISHED')
Expand Down